Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/prepare-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,15 @@ jobs:
if dependency_count != 1:
raise SystemExit("Expected exactly one agentic-core workspace dependency version in Cargo.toml")

dependency_pattern = (
r'(?m)^(vllm-responses = \{ path = "crates/vllm-responses", version = ")[^"]+(" \})$'
)
text, dependency_count = re.subn(dependency_pattern, rf'\g<1>{version}\g<2>', text, count=1)
if dependency_count != 1:
raise SystemExit("Expected exactly one vllm-responses workspace dependency version in Cargo.toml")

path.write_text(text)
print(f"Updated workspace and agentic-core versions to {version}")
print(f"Updated workspace, agentic-core, and vllm-responses versions to {version}")
PY

- name: Refresh lockfile
Expand Down
47 changes: 39 additions & 8 deletions .github/workflows/release-crates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,17 @@ jobs:
| jq -r '.packages[] | select(.name == "agentic-server-core") | .version')"
server_version="$(cargo metadata --locked --format-version 1 --no-deps \
| jq -r '.packages[] | select(.name == "agentic-server") | .version')"
responses_version="$(cargo metadata --locked --format-version 1 --no-deps \
| jq -r '.packages[] | select(.name == "vllm-responses") | .version')"

if [ -z "$core_version" ] || [ "$core_version" = "null" ]; then
echo "::error::agentic-server-core package not found in workspace metadata"
if [ -z "$core_version" ] || [ "$core_version" = "null" ] \
|| [ -z "$responses_version" ] || [ "$responses_version" = "null" ]; then
echo "::error::release package not found in workspace metadata"
exit 1
fi

if [ "$core_version" != "$server_version" ]; then
echo "::error::agentic-server-core ($core_version) and agentic-server ($server_version) versions differ"
if [ "$core_version" != "$server_version" ] || [ "$core_version" != "$responses_version" ]; then
echo "::error::release package versions differ"
exit 1
fi

Expand Down Expand Up @@ -113,7 +116,7 @@ jobs:
AGENTIC_RELEASE_VERSION: ${{ steps.version.outputs.version }}
run: |
set -euo pipefail
for crate in agentic-server-core agentic-server; do
for crate in vllm-responses agentic-server-core agentic-server; do
status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
--user-agent 'agentic-api-release (https://github.com/vllm-project/agentic-api)' \
--connect-timeout 10 --max-time 30 \
Expand Down Expand Up @@ -159,13 +162,41 @@ jobs:
if: inputs.dry_run
run: |
set -euo pipefail
cargo publish --dry-run --locked -p agentic-server-core
cargo package --no-verify --locked -p agentic-server \
--config 'patch.crates-io.agentic-server-core.path="crates/agentic-server-core"'
cargo publish --dry-run --locked -p vllm-responses
cargo publish --dry-run --locked -p agentic-server-core \
--config 'patch.crates-io.vllm-responses.path="crates/vllm-responses"'
cargo publish --dry-run --locked -p agentic-server \
--config 'patch.crates-io.agentic-server-core.path="crates/agentic-server-core"' \
--config 'patch.crates-io.vllm-responses.path="crates/vllm-responses"'
echo "Would release commit ${{ steps.target.outputs.sha }}"
echo "Would create tag ${{ steps.version.outputs.tag }}"
echo "Would create GitHub release ${{ steps.version.outputs.tag }} with generated notes"

- name: Publish vllm-responses
if: ${{ !inputs.dry_run }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish --locked -p vllm-responses

- name: Wait for vllm-responses index
if: ${{ !inputs.dry_run }}
run: |
set -euo pipefail
version="${{ steps.version.outputs.version }}"

for attempt in {1..30}; do
if cargo info "vllm-responses@$version" >/dev/null 2>&1; then
echo "vllm-responses $version is available in crates.io index"
exit 0
fi

echo "Waiting for crates.io index to expose vllm-responses $version (attempt $attempt/30)"
sleep 10
done

echo "::error::vllm-responses $version was not visible in crates.io index in time"
exit 1

- name: Publish agentic-server-core
if: ${{ !inputs.dry_run }}
env:
Expand Down
13 changes: 7 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,12 +616,13 @@ merge the concrete item with its retained fields and buffers. `slot.rs` dispatch
to those implementations after validating identity and lifecycle. Finalization
promotes each completed typed item once and preserves validated output-index order.

The pipeline's streaming entry is `process_line(ClassifiedSseLine)`. It normalizes and
validates a data line, applies the event to the slot keyed by its validated output
index, and returns at most one validated `EventFrame` for translation. For function
events, `accumulated_function_call(output_index)` exposes a borrowed typed call and its
folded arguments. `finish` applies SSE end-of-stream policy; `finalize` preserves the
status loaded from a complete JSON body.
The pipeline's HTTP/SSE streaming entry is `process_line(ClassifiedSseLine)` in
`source.rs`; native adapters enter through `process_inference_event(InferenceEvent)`
in `native.rs`. Both paths materialize the same internal frame and apply the same
strict lifecycle validation before translation. For function events,
`accumulated_function_call(output_index)` exposes a borrowed typed call and its folded
arguments. `finish` applies SSE end-of-stream policy; `finalize` preserves the status
loaded from a complete JSON body.

When adding an output-item kind, extend its typed construction and completion logic in
`types/io/output.rs`, the variants and exhaustive dispatch in `accumulator/active.rs`,
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to Agentic API are documented here.

## [Unreleased]

### Added

- Added the published `vllm-responses` crate: a transport-independent typed
inference-event contract for vLLM and Agentic API integrations (#359).

## [0.8.0] - 2026-09-19

### Added
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pedantic = { level = "warn", priority = -1 }

[workspace.dependencies]
agentic-core = { package = "agentic-server-core", path = "crates/agentic-server-core", version = "0.8.0" }
vllm-responses = { path = "crates/vllm-responses", version = "0.8.0" }
async-stream = "0.3"
axum = { version = "0.8", features = ["ws"] }
either = "1"
Expand Down
1 change: 1 addition & 0 deletions crates/agentic-server-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ default = []
openapi = ["dep:utoipa"]

[dependencies]
vllm-responses.workspace = true
utoipa = { workspace = true, optional = true }

async-stream.workspace = true
Expand Down
28 changes: 5 additions & 23 deletions crates/agentic-server-core/src/executor/accumulator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ use identity::{invalid_lifecycle, invalid_lifecycle_or_id, invalid_stream, item_
mod completion;
mod details;
mod json;
mod native;
#[cfg(test)]
mod native_tests;
mod slot;
mod source;

use active::ActiveItem;
use slot::{OutputIndex, SlotMap, SlotState};
Expand Down Expand Up @@ -296,27 +300,6 @@ impl ResponseAccumulator {
Ok(acc)
}

/// Processes pre-collected raw SSE lines synchronously.
///
/// Useful when lines have already been buffered (e.g. replaying a recorded stream).
/// Prefer [`from_stream`](Self::from_stream) for live async streams.
/// Malformed data frames are skipped for compatibility.
///
/// # Errors
/// Returns [`ExecutorError::InvalidRequest`] when repeated authoritative
/// output-item content conflicts with the previously accumulated value.
pub fn from_sse_lines(
lines: impl IntoIterator<Item = String>,
conversation_id: Option<&str>,
) -> ExecutorResult<Self> {
let mut acc = Self::new(uuid7_str("resp_"), conversation_id.map(str::to_string));
for line in lines {
let _ = acc.process_line(SseLine::parse(&line))?;
}
acc.finalize_all()?;
Ok(acc)
}

/// Finalizes all streaming items in upstream `output_index` order.
pub(crate) fn finalize_all(&mut self) -> ExecutorResult<()> {
self.output
Expand All @@ -335,8 +318,7 @@ impl ResponseAccumulator {
}
return Ok(None);
};
let disposition = self.process_normalized_event(&frame)?;
Ok(disposition.into_frame(frame))
self.process_event_frame(frame)
}

fn process_normalized_event(&mut self, frame: &EventFrame) -> ExecutorResult<EventDisposition> {
Expand Down
Loading
Loading