@cuylabs/agent-foundry-agentserver-responses 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ # Protocol Behavior
2
+
3
+ This package hosts the Responses protocol endpoints required by Azure AI
4
+ Foundry Hosted Agents.
5
+
6
+ ## Endpoints
7
+
8
+ | Method | Route | Behavior |
9
+ | --- | --- | --- |
10
+ | `POST` | `/responses` | Create a response |
11
+ | `GET` | `/responses/{response_id}` | Retrieve a stored or in-flight response snapshot |
12
+ | `GET` | `/responses/{response_id}?stream=true` | Replay stored stream events for a background streaming response |
13
+ | `GET` | `/responses/{response_id}?stream=true&starting_after=N` | Replay only events with `sequence_number > N` |
14
+ | `POST` | `/responses/{response_id}/cancel` | Cancel an in-flight background response |
15
+ | `DELETE` | `/responses/{response_id}` | Delete a stored response |
16
+ | `GET` | `/responses/{response_id}/input_items` | List historical and current input items |
17
+ | `GET` | `/responses/docs/openapi.json` | Return the package OpenAPI document or a handler-provided override |
18
+
19
+ ## Modes
20
+
21
+ The create request controls three important flags:
22
+
23
+ | Flag | Meaning |
24
+ | --- | --- |
25
+ | `stream` | Send response events as Server-Sent Events |
26
+ | `background` | Return before execution finishes and keep the response active |
27
+ | `store` | Persist response state through the configured `ResponseProvider` |
28
+
29
+ `background=true` requires `store=true`, matching the hosted-agent behavior.
30
+ `stream_options` requires `stream=true`.
31
+
32
+ ## IDs And Isolation
33
+
34
+ Response IDs use the `caresp` prefix. Route parameters and
35
+ `previous_response_id` values are validated before storage access.
36
+
37
+ The host resolves Agent Server user and chat isolation from request headers and
38
+ passes the isolation context to providers. Active in-flight responses are also
39
+ keyed by isolation, so one scoped caller cannot read or cancel another scoped
40
+ caller's active response.
41
+
42
+ ## Input Items And History
43
+
44
+ Create input is normalized into response input items and stored with the
45
+ response when `store` is enabled. `ResponseContext` can retrieve current input,
46
+ previous response history, and referenced items through the configured provider.
47
+
48
+ History lookup is provider-backed. Foundry storage resolves history by
49
+ `previous_response_id` and conversation ID. The in-memory provider mirrors that
50
+ behavior for local tests and development.
51
+
52
+ ## OpenAPI
53
+
54
+ The package includes a hand-maintained OpenAPI document for the hosted endpoints
55
+ in `src/openapi.ts`. A handler can override the document by implementing
56
+ `getOpenApi()`.
57
+
58
+ The long-term direction is to generate or validate the TypeScript model surface
59
+ from the same schema source used by the platform, while preserving the
60
+ provider/handler contracts.
@@ -0,0 +1,106 @@
1
+ # Python Parity And Production Readiness
2
+
3
+ This package tracks Python `azure-ai-agentserver-responses`, but the Python
4
+ package is currently more mature. Use this document as the working parity map.
5
+
6
+ ## Comparison Scope
7
+
8
+ Use the Python packages as responsibility references, not as file-layout
9
+ templates:
10
+
11
+ | TypeScript | Python | Status |
12
+ | --- | --- | --- |
13
+ | `@cuylabs/agent-foundry-agentserver-core` | `azure-ai-agentserver-core` | Same host-foundation role, but Express/Node instead of ASGI/Starlette/Hypercorn |
14
+ | `@cuylabs/agent-foundry-agentserver-responses` | `azure-ai-agentserver-responses` | Same Responses protocol role and depends on the TS core package |
15
+ | `@cuylabs/agent-foundry-hosting/responses` | No direct Python package peer | agents-ts bridge that maps local `agent-core` events into Responses events |
16
+
17
+ This means `agent-foundry-hosting/responses` should be compared against
18
+ `agent-core` and `agent-server` behavior, while protocol correctness should be
19
+ checked in `agent-foundry-agentserver-responses`.
20
+
21
+ ## Source Of Truth
22
+
23
+ The Python package points at the Azure REST API specs TypeSpec source through
24
+ `type_spec/tsp-location.yaml`:
25
+
26
+ ```yaml
27
+ directory: specification/ai-foundry/data-plane/Foundry/src/sdk-service-agentserver-contracts
28
+ entrypointFile: client.tsp
29
+ additionalDirectories:
30
+ - specification/ai-foundry/data-plane/Foundry/src/openai-responses
31
+ - specification/ai-foundry/data-plane/Foundry/src/openai-conversations
32
+ - specification/ai-foundry/data-plane/Foundry/src/tools
33
+ - specification/ai-foundry/data-plane/Foundry/src/common
34
+ - specification/ai-foundry/data-plane/Foundry/src/memory-stores
35
+ ```
36
+
37
+ For production parity, TypeScript should eventually generate or validate its
38
+ protocol models from the same TypeSpec/OpenAPI source instead of relying only on
39
+ hand-written structural interfaces.
40
+
41
+ ## Current TS Coverage
42
+
43
+ | Area | TS status |
44
+ | --- | --- |
45
+ | Express host and route surface | Implemented |
46
+ | Create, get, delete, cancel, input-items endpoints | Implemented |
47
+ | SSE response framing | Implemented |
48
+ | Replay cursor `starting_after` | Implemented |
49
+ | `TextResponse` convenience helper | Implemented |
50
+ | `ResponseContext` input text, input items, history | Implemented |
51
+ | `ResponseProvider` and `ResponseStreamProvider` contracts | Implemented |
52
+ | Foundry HTTP storage provider | Implemented |
53
+ | In-memory provider for dev/test | Implemented |
54
+ | Agent Server headers, isolation, tracing hook-up | Implemented foundation through core; responses-specific contract coverage still partial |
55
+ | `agent-core` bridge | Implemented in `@cuylabs/agent-foundry-hosting/responses` |
56
+ | TypeSpec-generated request/response models | Not implemented |
57
+ | Generated schema validation | Not implemented |
58
+ | Full `ResponseEventStream` builder API | Not implemented |
59
+ | SSE keep-alive option | Not implemented |
60
+ | Python contract-suite parity | Partial |
61
+
62
+ ## Important Gaps Before Calling This Fully Production-Equivalent
63
+
64
+ The TS source now follows the same broad domains as Python:
65
+ `hosting`, `store`, and `streaming`. It intentionally does not copy Python's
66
+ private module names one-for-one, and the model layer remains hand-written until
67
+ the TypeSpec generation work is added.
68
+
69
+ The current TS implementation is a good protocol host foundation, but it is not
70
+ yet as hardened as the Python SDK.
71
+
72
+ First, request validation is still mostly structural and semantic. Python
73
+ generates validators from the TypeSpec/OpenAPI model and rejects malformed
74
+ payloads earlier and with richer details.
75
+
76
+ Second, Python has a full lifecycle/state-machine layer around emitted events.
77
+ It validates first-event rules, output item event shape, terminal transitions,
78
+ and output manipulation invariants. TS currently normalizes only the minimum
79
+ needed event shape.
80
+
81
+ Third, Python has a broad contract suite covering mode matrices, stream replay,
82
+ disconnect behavior, cancellation consistency, persistence failure behavior,
83
+ chat isolation, eager eviction, and OpenAI wire compliance. TS has focused unit
84
+ and host tests, but not yet the equivalent contract matrix.
85
+
86
+ Fourth, Python exposes richer handler ergonomics through `ResponseEventStream`
87
+ and builder helpers for messages, function calls, reasoning items, annotations,
88
+ images, and structured outputs. TS currently supports raw events and
89
+ `TextResponse`.
90
+
91
+ Fifth, Python's Foundry storage provider is layered on the Azure SDK pipeline,
92
+ including retry, request logging, tracing, and storage-specific serialization.
93
+ The TS provider follows the same HTTP endpoint shape, but it currently uses a
94
+ direct `fetch` implementation and needs the same production transport hardening.
95
+
96
+ ## Recommended Next Work
97
+
98
+ 1. Add TypeSpec/OpenAPI-based model generation or runtime validation for
99
+ `CreateResponse`, response items, and stream events.
100
+ 2. Port the Python mode matrix and cross-API contract tests into TS.
101
+ 3. Add a TS `ResponseEventStream` builder API instead of expecting raw events
102
+ for non-text outputs.
103
+ 4. Add lifecycle validation for handler-emitted events.
104
+ 5. Add persistence-failure tests and storage error mapping parity.
105
+ 6. Add SSE keep-alive support if hosted Foundry requires long-lived streams
106
+ through infrastructure that closes idle connections.
package/docs/store.md ADDED
@@ -0,0 +1,88 @@
1
+ # Storage
2
+
3
+ Durable storage is the persistence layer behind the Responses protocol. It is
4
+ used for response snapshots, input items, history item lookup, and response
5
+ retrieval after the current request finishes.
6
+
7
+ ## Default Storage
8
+
9
+ If no `store` is provided, the host uses `InMemoryResponseProvider`.
10
+
11
+ This is useful for local development and tests, but it is not durable. State is
12
+ lost when the process exits and it is not shared across replicas.
13
+
14
+ ```ts
15
+ await runResponsesServer({
16
+ handler,
17
+ });
18
+ ```
19
+
20
+ ## Foundry Storage
21
+
22
+ For Foundry-hosted production, use `FoundryStorageProvider`. It is an
23
+ HTTP-backed provider for the Azure AI Foundry storage API. The provider derives
24
+ its storage URL from `FOUNDRY_PROJECT_ENDPOINT` by appending `/storage/`.
25
+
26
+ ```ts
27
+ import { DefaultAzureCredential } from "@azure/identity";
28
+ import { runResponsesServer } from "@cuylabs/agent-foundry-agentserver-responses";
29
+ import {
30
+ FoundryStorageProvider,
31
+ } from "@cuylabs/agent-foundry-agentserver-responses/store";
32
+
33
+ await runResponsesServer({
34
+ handler,
35
+ store: new FoundryStorageProvider({
36
+ credential: new DefaultAzureCredential(),
37
+ }),
38
+ });
39
+ ```
40
+
41
+ You can also pass `projectEndpoint` or `storageBaseUrl` explicitly:
42
+
43
+ ```ts
44
+ new FoundryStorageProvider({
45
+ credential,
46
+ projectEndpoint: "https://example.services.ai.azure.com/projects/my-project",
47
+ });
48
+ ```
49
+
50
+ ## Foundry Storage Endpoints
51
+
52
+ The provider uses the same storage API shape as the Python SDK:
53
+
54
+ | Operation | Storage endpoint |
55
+ | --- | --- |
56
+ | Create response | `POST /storage/responses` |
57
+ | Get response | `GET /storage/responses/{response_id}` |
58
+ | Update response | `POST /storage/responses/{response_id}` |
59
+ | Delete response | `DELETE /storage/responses/{response_id}` |
60
+ | List input items | `GET /storage/responses/{response_id}/input_items` |
61
+ | Batch item lookup | `POST /storage/items/batch/retrieve` |
62
+ | History item IDs | `GET /storage/history/item_ids` |
63
+
64
+ The provider also forwards Agent Server isolation headers so scoped user/chat
65
+ state is resolved by Foundry storage.
66
+
67
+ ## Stream Replay Storage
68
+
69
+ `ResponseProvider` handles response snapshots and item history.
70
+ `ResponseStreamProvider` is a separate optional capability for persisted SSE
71
+ event replay.
72
+
73
+ If the configured durable provider does not implement `ResponseStreamProvider`,
74
+ the host creates an in-memory stream provider fallback. That means the response
75
+ snapshot can be durable while replay of already-emitted SSE events is only
76
+ available inside the current process.
77
+
78
+ Implement `ResponseStreamProvider` when durable stream replay matters across
79
+ container restarts or replicas.
80
+
81
+ ## Custom Storage
82
+
83
+ You only need your own database when Foundry storage is not available or when
84
+ you need custom retention, compliance, auditing, or placement requirements.
85
+
86
+ Implement `ResponseProvider` for response snapshots and history. Implement
87
+ `ResponseStreamProvider` too if you need durable `GET /responses/{id}?stream=true`
88
+ replay.
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@cuylabs/agent-foundry-agentserver-responses",
3
+ "version": "4.9.0",
4
+ "description": "TypeScript Foundry Agent Server Responses-protocol host (mirrors azure-ai-agentserver-responses)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./store": {
15
+ "types": "./dist/store/index.d.ts",
16
+ "import": "./dist/store/index.js",
17
+ "default": "./dist/store/index.js"
18
+ },
19
+ "./streaming": {
20
+ "types": "./dist/streaming/index.d.ts",
21
+ "import": "./dist/streaming/index.js",
22
+ "default": "./dist/streaming/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "docs",
28
+ "samples",
29
+ "README.md"
30
+ ],
31
+ "dependencies": {
32
+ "express": "^5.0.0",
33
+ "@cuylabs/agent-foundry-agentserver-core": "^4.9.0"
34
+ },
35
+ "devDependencies": {
36
+ "@opentelemetry/api": "^1.9.0",
37
+ "@types/express": "^5.0.0",
38
+ "@types/node": "^22.0.0",
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.7.0",
41
+ "vitest": "^4.0.18"
42
+ },
43
+ "keywords": [
44
+ "agent",
45
+ "azure",
46
+ "foundry",
47
+ "hosted-agent",
48
+ "responses-protocol",
49
+ "agentserver",
50
+ "express",
51
+ "sse"
52
+ ],
53
+ "author": "cuylabs",
54
+ "license": "Apache-2.0",
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "https://github.com/cuylabs-ai/agents-ts.git",
58
+ "directory": "packages/agent-foundry-agentserver-responses"
59
+ },
60
+ "engines": {
61
+ "node": ">=20"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "scripts": {
67
+ "build": "tsup src/index.ts src/store/index.ts src/streaming/index.ts --format esm --dts --clean",
68
+ "dev": "tsup src/index.ts src/store/index.ts src/streaming/index.ts --format esm --dts --watch",
69
+ "typecheck": "tsc --noEmit",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "clean": "rm -rf dist"
73
+ }
74
+ }
@@ -0,0 +1,30 @@
1
+ # Agent Server Responses Samples
2
+
3
+ These samples mirror the most important Python `azure-ai-agentserver-responses`
4
+ patterns in TypeScript.
5
+
6
+ ## Samples
7
+
8
+ | Sample | Pattern | Purpose |
9
+ | --- | --- | --- |
10
+ | [sample_01_getting_started.ts](./sample_01_getting_started.ts) | `TextResponse` | Minimal echo server |
11
+ | [sample_02_streaming_text.ts](./sample_02_streaming_text.ts) | `TextResponse` + async iterable | Token-style streaming |
12
+ | [sample_03_raw_events.ts](./sample_03_raw_events.ts) | Raw `ResponseStreamEvent` | Function-call style event control |
13
+ | [sample_04_foundry_storage.ts](./sample_04_foundry_storage.ts) | `FoundryStorageProvider` | Durable Foundry response storage |
14
+ | [sample_05_agent_core_bridge.ts](./sample_05_agent_core_bridge.ts) | `agent-foundry-hosting/responses` | Bridge an `agent-core` agent into this protocol host |
15
+
16
+ ## Running Locally
17
+
18
+ Build the workspace first, then run a sample with a TypeScript runner such as
19
+ `tsx` from the workspace root:
20
+
21
+ ```bash
22
+ pnpm --filter @cuylabs/agent-foundry-agentserver-responses build
23
+ pnpm exec tsx packages/agent-foundry-agentserver-responses/samples/sample_01_getting_started.ts
24
+ ```
25
+
26
+ The Foundry storage sample expects Azure credentials and
27
+ `FOUNDRY_PROJECT_ENDPOINT`.
28
+
29
+ The agent-core bridge sample includes a placeholder `myAgent` import. Replace
30
+ that import with the local agent you want to host.
@@ -0,0 +1,16 @@
1
+ import {
2
+ ResponseContext,
3
+ TextResponse,
4
+ runResponsesServer,
5
+ type CreateResponse,
6
+ } from "@cuylabs/agent-foundry-agentserver-responses";
7
+
8
+ await runResponsesServer({
9
+ port: Number.parseInt(process.env.PORT ?? "8088", 10),
10
+ async handler(request: CreateResponse, context: ResponseContext) {
11
+ const text = await context.getInputText();
12
+ return new TextResponse(context, request, {
13
+ text: `Echo: ${text}`,
14
+ });
15
+ },
16
+ });
@@ -0,0 +1,23 @@
1
+ import {
2
+ ResponseContext,
3
+ TextResponse,
4
+ runResponsesServer,
5
+ type CreateResponse,
6
+ } from "@cuylabs/agent-foundry-agentserver-responses";
7
+
8
+ await runResponsesServer({
9
+ port: Number.parseInt(process.env.PORT ?? "8088", 10),
10
+ async handler(request: CreateResponse, context: ResponseContext) {
11
+ const text = await context.getInputText();
12
+ return new TextResponse(context, request, {
13
+ text: streamTokens(`Streaming echo: ${text}`),
14
+ });
15
+ },
16
+ });
17
+
18
+ async function* streamTokens(text: string): AsyncGenerator<string> {
19
+ for (const token of text.split(/(\s+)/u)) {
20
+ await new Promise((resolve) => setTimeout(resolve, 25));
21
+ yield token;
22
+ }
23
+ }
@@ -0,0 +1,58 @@
1
+ import {
2
+ ResponseContext,
3
+ createResponseObject,
4
+ newOutputMessageItemId,
5
+ runResponsesServer,
6
+ type CreateResponse,
7
+ type OutputItem,
8
+ type ResponseStreamEvent,
9
+ } from "@cuylabs/agent-foundry-agentserver-responses";
10
+
11
+ await runResponsesServer({
12
+ port: Number.parseInt(process.env.PORT ?? "8088", 10),
13
+ async handler(request: CreateResponse, context: ResponseContext) {
14
+ return emitFunctionCall(request, context);
15
+ },
16
+ });
17
+
18
+ async function* emitFunctionCall(
19
+ request: CreateResponse,
20
+ context: ResponseContext,
21
+ ): AsyncGenerator<ResponseStreamEvent> {
22
+ const response = createResponseObject({
23
+ id: context.responseId,
24
+ request,
25
+ status: "in_progress",
26
+ createdAt: context.createdAt,
27
+ });
28
+ yield { type: "response.created", response };
29
+ yield { type: "response.in_progress", response };
30
+
31
+ const item: OutputItem = {
32
+ id: newOutputMessageItemId(context.responseId),
33
+ type: "function_call",
34
+ status: "completed",
35
+ name: "get_weather",
36
+ call_id: "call_001",
37
+ arguments: JSON.stringify({ location: "Seattle", unit: "fahrenheit" }),
38
+ };
39
+ yield {
40
+ type: "response.output_item.added",
41
+ output_index: 0,
42
+ item,
43
+ };
44
+ yield {
45
+ type: "response.output_item.done",
46
+ output_index: 0,
47
+ item,
48
+ };
49
+
50
+ yield {
51
+ type: "response.completed",
52
+ response: {
53
+ ...response,
54
+ status: "completed",
55
+ output: [item],
56
+ },
57
+ };
58
+ }
@@ -0,0 +1,26 @@
1
+ import { DefaultAzureCredential } from "@azure/identity";
2
+ import {
3
+ ResponseContext,
4
+ TextResponse,
5
+ runResponsesServer,
6
+ type CreateResponse,
7
+ } from "@cuylabs/agent-foundry-agentserver-responses";
8
+ import {
9
+ FoundryStorageProvider,
10
+ } from "@cuylabs/agent-foundry-agentserver-responses/store";
11
+
12
+ const credential = new DefaultAzureCredential();
13
+
14
+ await runResponsesServer({
15
+ port: Number.parseInt(process.env.PORT ?? "8088", 10),
16
+ store: new FoundryStorageProvider({
17
+ credential,
18
+ }),
19
+ async handler(request: CreateResponse, context: ResponseContext) {
20
+ const text = await context.getInputText();
21
+ const history = await context.getHistory();
22
+ return new TextResponse(context, request, {
23
+ text: `Echo: ${text}\nHistory items available: ${history.length}`,
24
+ });
25
+ },
26
+ });
@@ -0,0 +1,15 @@
1
+ import { runResponsesServer } from "@cuylabs/agent-foundry-agentserver-responses";
2
+ import { createResponsesHandlerForAgent } from "@cuylabs/agent-foundry-hosting/responses";
3
+ import {
4
+ InProcessAgentServer,
5
+ createAgentServerAdapter,
6
+ } from "@cuylabs/agent-server";
7
+
8
+ import { myAgent } from "./shared/my-agent.js";
9
+
10
+ const server = new InProcessAgentServer(createAgentServerAdapter(myAgent));
11
+
12
+ await runResponsesServer({
13
+ port: Number.parseInt(process.env.PORT ?? "8088", 10),
14
+ handler: createResponsesHandlerForAgent(server),
15
+ });