@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.
- package/LICENSE +201 -0
- package/README.md +115 -0
- package/dist/chunk-24VMS6GY.js +513 -0
- package/dist/chunk-DGMWFFNQ.js +473 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +1163 -0
- package/dist/store/index.d.ts +134 -0
- package/dist/store/index.js +14 -0
- package/dist/streaming/index.d.ts +27 -0
- package/dist/streaming/index.js +14 -0
- package/dist/types-Dvs2F85g.d.ts +254 -0
- package/docs/README.md +72 -0
- package/docs/agent-core-bridge.md +48 -0
- package/docs/hosting.md +72 -0
- package/docs/protocol.md +60 -0
- package/docs/python-parity.md +106 -0
- package/docs/store.md +88 -0
- package/package.json +74 -0
- package/samples/README.md +30 -0
- package/samples/sample_01_getting_started.ts +16 -0
- package/samples/sample_02_streaming_text.ts +23 -0
- package/samples/sample_03_raw_events.ts +58 -0
- package/samples/sample_04_foundry_storage.ts +26 -0
- package/samples/sample_05_agent_core_bridge.ts +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AGENTSERVER_RESPONSES_PACKAGE_VERSION,
|
|
3
|
+
FoundryStorageError,
|
|
4
|
+
FoundryStorageNotFoundError,
|
|
5
|
+
FoundryStorageProvider,
|
|
6
|
+
FoundryStorageSettings,
|
|
7
|
+
InMemoryResponseProvider
|
|
8
|
+
} from "./chunk-DGMWFFNQ.js";
|
|
9
|
+
import {
|
|
10
|
+
RESPONSE_ID_HEADER,
|
|
11
|
+
SESSION_ID_HEADER,
|
|
12
|
+
TextResponse,
|
|
13
|
+
applyCreateResponseDefaults,
|
|
14
|
+
createResponseObject,
|
|
15
|
+
createSseEncoderState,
|
|
16
|
+
deriveSessionId,
|
|
17
|
+
encodeKeepAliveComment,
|
|
18
|
+
encodeSseEvent,
|
|
19
|
+
extractPartitionKey,
|
|
20
|
+
extractTextFromItems,
|
|
21
|
+
getInputExpanded,
|
|
22
|
+
isValidId,
|
|
23
|
+
newId,
|
|
24
|
+
newMessageItemId,
|
|
25
|
+
newOutputMessageItemId,
|
|
26
|
+
newResponseId,
|
|
27
|
+
normalizeCreateResponse,
|
|
28
|
+
normalizeResponseInput,
|
|
29
|
+
resolveAgentReference,
|
|
30
|
+
resolveConversationId,
|
|
31
|
+
resolveResponseId,
|
|
32
|
+
resolveSessionId,
|
|
33
|
+
startResponsesSseResponse,
|
|
34
|
+
toOutputItem,
|
|
35
|
+
validateResponseId
|
|
36
|
+
} from "./chunk-24VMS6GY.js";
|
|
37
|
+
|
|
38
|
+
// src/host.ts
|
|
39
|
+
import express from "express";
|
|
40
|
+
import {
|
|
41
|
+
AGENTSERVER_CORE_PACKAGE_VERSION,
|
|
42
|
+
agentServerLoggingMiddleware,
|
|
43
|
+
agentServerRequestHeadersMiddleware,
|
|
44
|
+
buildPlatformServerHeader,
|
|
45
|
+
closeServerWithTimeout,
|
|
46
|
+
configureAgentServerObservability,
|
|
47
|
+
configureExpressAgentServerApp,
|
|
48
|
+
defaultAgentServerLogger,
|
|
49
|
+
errorMiddleware,
|
|
50
|
+
formatError as formatError2,
|
|
51
|
+
logAgentServerStartupConfiguration,
|
|
52
|
+
notFoundMiddleware,
|
|
53
|
+
resolveAgentServerConfig
|
|
54
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
55
|
+
|
|
56
|
+
// src/hosting/handler-provider.ts
|
|
57
|
+
function makeHandlerProvider(input) {
|
|
58
|
+
if (typeof input === "function") {
|
|
59
|
+
if (input.length === 0) {
|
|
60
|
+
return () => {
|
|
61
|
+
const handler = input();
|
|
62
|
+
return handler;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return () => ({ handle: input });
|
|
66
|
+
}
|
|
67
|
+
return () => input;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/hosting/http-utils.ts
|
|
71
|
+
import {
|
|
72
|
+
createErrorBody
|
|
73
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
74
|
+
function supportsStreamProvider(provider) {
|
|
75
|
+
return typeof provider.appendStreamEvent === "function" && typeof provider.getStreamEvents === "function" && typeof provider.deleteStreamEvents === "function";
|
|
76
|
+
}
|
|
77
|
+
function queryParameters(req) {
|
|
78
|
+
const params = {};
|
|
79
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
params[key] = value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return params;
|
|
85
|
+
}
|
|
86
|
+
function parseLimit(value) {
|
|
87
|
+
const raw = typeof value === "string" ? Number.parseInt(value, 10) : 20;
|
|
88
|
+
if (!Number.isInteger(raw) || raw < 1) {
|
|
89
|
+
return 20;
|
|
90
|
+
}
|
|
91
|
+
return Math.min(raw, 100);
|
|
92
|
+
}
|
|
93
|
+
function queryString(value) {
|
|
94
|
+
return typeof value === "string" && value ? value : void 0;
|
|
95
|
+
}
|
|
96
|
+
function modeFlagsFromResponse(response) {
|
|
97
|
+
return {
|
|
98
|
+
stream: response.stream === true,
|
|
99
|
+
background: response.background === true,
|
|
100
|
+
store: response.store !== false
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function scopedActiveKey(responseId, isolation) {
|
|
104
|
+
return `${isolation.chatKey ?? ""}|${isolation.userKey ?? ""}|${responseId}`;
|
|
105
|
+
}
|
|
106
|
+
function errorMessage(error) {
|
|
107
|
+
if (error instanceof Error) {
|
|
108
|
+
return error.message;
|
|
109
|
+
}
|
|
110
|
+
return String(error);
|
|
111
|
+
}
|
|
112
|
+
function jsonParseErrorMiddleware(error, req, res, next) {
|
|
113
|
+
if (error instanceof SyntaxError) {
|
|
114
|
+
res.status(400).json(
|
|
115
|
+
createErrorBody("invalid_request", "request body must be valid JSON", {
|
|
116
|
+
type: "invalid_request_error",
|
|
117
|
+
requestId: req.agentServerRequestId
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
next(error);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/hosting/routes.ts
|
|
126
|
+
import {
|
|
127
|
+
healthzHandler,
|
|
128
|
+
methodNotAllowed,
|
|
129
|
+
readinessHandler
|
|
130
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
131
|
+
|
|
132
|
+
// src/openapi.ts
|
|
133
|
+
function buildResponsesOpenApiSpec(options = {}) {
|
|
134
|
+
const title = options.appName ?? "Foundry Responses Host";
|
|
135
|
+
return {
|
|
136
|
+
openapi: "3.0.3",
|
|
137
|
+
info: {
|
|
138
|
+
title,
|
|
139
|
+
version: "1.0.0"
|
|
140
|
+
},
|
|
141
|
+
paths: {
|
|
142
|
+
"/responses": {
|
|
143
|
+
post: {
|
|
144
|
+
summary: "Create a response",
|
|
145
|
+
responses: {
|
|
146
|
+
"200": { description: "Response created" }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"/responses/{response_id}": {
|
|
151
|
+
get: {
|
|
152
|
+
summary: "Get a response",
|
|
153
|
+
responses: {
|
|
154
|
+
"200": { description: "Response snapshot or SSE replay" },
|
|
155
|
+
"404": { description: "Response not found" }
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
delete: {
|
|
159
|
+
summary: "Delete a response",
|
|
160
|
+
responses: {
|
|
161
|
+
"200": { description: "Response deleted" },
|
|
162
|
+
"404": { description: "Response not found" }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"/responses/{response_id}/cancel": {
|
|
167
|
+
post: {
|
|
168
|
+
summary: "Cancel a background response",
|
|
169
|
+
responses: {
|
|
170
|
+
"200": { description: "Response cancelled" },
|
|
171
|
+
"404": { description: "Response not found" }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"/responses/{response_id}/input_items": {
|
|
176
|
+
get: {
|
|
177
|
+
summary: "List response input items",
|
|
178
|
+
responses: {
|
|
179
|
+
"200": { description: "Input items" },
|
|
180
|
+
"404": { description: "Response not found" }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
"/readiness": {
|
|
185
|
+
get: {
|
|
186
|
+
summary: "Readiness probe",
|
|
187
|
+
responses: { "200": { description: "Healthy" } }
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"/healthz": {
|
|
191
|
+
get: {
|
|
192
|
+
summary: "Liveness probe",
|
|
193
|
+
responses: { "200": { description: "Healthy" } }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/hosting/routes/cancel-response.ts
|
|
201
|
+
import {
|
|
202
|
+
createErrorBody as createErrorBody3,
|
|
203
|
+
resolveAgentIsolation
|
|
204
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
205
|
+
|
|
206
|
+
// src/hosting/routes/route-utils.ts
|
|
207
|
+
import { createErrorBody as createErrorBody2 } from "@cuylabs/agent-foundry-agentserver-core";
|
|
208
|
+
function routeResponseId(req) {
|
|
209
|
+
const responseId = req.params.responseId;
|
|
210
|
+
return Array.isArray(responseId) ? responseId[0] ?? "" : responseId ?? "";
|
|
211
|
+
}
|
|
212
|
+
function validateRouteResponseId(responseId, res) {
|
|
213
|
+
const validation = isValidId(responseId, ["caresp"]);
|
|
214
|
+
if (validation.valid) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
res.status(400).json(
|
|
218
|
+
createErrorBody2(
|
|
219
|
+
"invalid_parameters",
|
|
220
|
+
validation.error ?? "invalid response_id"
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/hosting/routes/cancel-response.ts
|
|
227
|
+
function cancelResponseHandler(options) {
|
|
228
|
+
return async (req, res) => {
|
|
229
|
+
const responseId = routeResponseId(req);
|
|
230
|
+
if (!validateRouteResponseId(responseId, res)) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const isolation = resolveAgentIsolation((name) => req.header(name));
|
|
234
|
+
const activeExecution = options.active.get(
|
|
235
|
+
scopedActiveKey(responseId, isolation)
|
|
236
|
+
);
|
|
237
|
+
if (!activeExecution) {
|
|
238
|
+
res.status(404).json(createErrorBody3("not_found", "Response not found"));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (!activeExecution.modeFlags.background) {
|
|
242
|
+
res.status(400).json(
|
|
243
|
+
createErrorBody3(
|
|
244
|
+
"invalid_request",
|
|
245
|
+
"Cannot cancel a synchronous response."
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
activeExecution.controller.abort();
|
|
251
|
+
const response = await activeExecution.done.catch(
|
|
252
|
+
() => activeExecution.response
|
|
253
|
+
);
|
|
254
|
+
res.status(200).json(response);
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/hosting/routes/create-response.ts
|
|
259
|
+
import {
|
|
260
|
+
addProtectedHeaders,
|
|
261
|
+
collectClientHeaders,
|
|
262
|
+
createErrorBody as createErrorBody4,
|
|
263
|
+
formatError,
|
|
264
|
+
resolveAgentIsolation as resolveAgentIsolation2
|
|
265
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
266
|
+
|
|
267
|
+
// src/context.ts
|
|
268
|
+
var ResponseContext = class {
|
|
269
|
+
responseId;
|
|
270
|
+
request;
|
|
271
|
+
createdAt;
|
|
272
|
+
modeFlags;
|
|
273
|
+
sessionId;
|
|
274
|
+
previousResponseId;
|
|
275
|
+
conversationId;
|
|
276
|
+
clientHeaders;
|
|
277
|
+
queryParameters;
|
|
278
|
+
isolation;
|
|
279
|
+
isShutdownRequested = false;
|
|
280
|
+
provider;
|
|
281
|
+
historyLimit;
|
|
282
|
+
inputItems;
|
|
283
|
+
resolvedInputCache;
|
|
284
|
+
historyCache;
|
|
285
|
+
constructor(options) {
|
|
286
|
+
this.responseId = options.responseId;
|
|
287
|
+
this.request = options.request;
|
|
288
|
+
this.createdAt = options.createdAt ?? Math.floor(Date.now() / 1e3);
|
|
289
|
+
this.modeFlags = options.modeFlags;
|
|
290
|
+
this.sessionId = options.sessionId ?? options.responseId;
|
|
291
|
+
this.provider = options.provider;
|
|
292
|
+
this.inputItems = options.inputItems ?? getInputExpanded(options.request, options.responseId);
|
|
293
|
+
if (options.previousResponseId !== void 0) {
|
|
294
|
+
this.previousResponseId = options.previousResponseId;
|
|
295
|
+
}
|
|
296
|
+
if (options.conversationId !== void 0) {
|
|
297
|
+
this.conversationId = options.conversationId;
|
|
298
|
+
}
|
|
299
|
+
this.historyLimit = options.historyLimit ?? 100;
|
|
300
|
+
this.clientHeaders = options.clientHeaders ?? {};
|
|
301
|
+
this.queryParameters = options.queryParameters ?? {};
|
|
302
|
+
this.isolation = options.isolation ?? {};
|
|
303
|
+
}
|
|
304
|
+
async getInputItems(options = {}) {
|
|
305
|
+
const resolveReferences = options.resolveReferences !== false;
|
|
306
|
+
if (!resolveReferences) {
|
|
307
|
+
return this.inputItems;
|
|
308
|
+
}
|
|
309
|
+
if (this.resolvedInputCache) {
|
|
310
|
+
return this.resolvedInputCache;
|
|
311
|
+
}
|
|
312
|
+
const resolved = [];
|
|
313
|
+
const references = [];
|
|
314
|
+
for (const item of this.inputItems) {
|
|
315
|
+
if (item.type === "item_reference" && typeof item.id === "string") {
|
|
316
|
+
references.push({ index: resolved.length, id: item.id });
|
|
317
|
+
resolved.push(item);
|
|
318
|
+
} else {
|
|
319
|
+
resolved.push(item);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (references.length > 0 && this.provider) {
|
|
323
|
+
const items = await this.provider.getItems(
|
|
324
|
+
references.map((reference) => reference.id),
|
|
325
|
+
{ isolation: this.isolation }
|
|
326
|
+
);
|
|
327
|
+
for (let i = 0; i < references.length; i += 1) {
|
|
328
|
+
const reference = references[i];
|
|
329
|
+
const item = items[i];
|
|
330
|
+
if (reference && item) {
|
|
331
|
+
resolved[reference.index] = item;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
this.resolvedInputCache = resolved.filter(
|
|
336
|
+
(item) => item.type !== "item_reference"
|
|
337
|
+
);
|
|
338
|
+
return this.resolvedInputCache;
|
|
339
|
+
}
|
|
340
|
+
async getInputText(options = {}) {
|
|
341
|
+
const items = await this.getInputItems(options);
|
|
342
|
+
return extractTextFromItems(items);
|
|
343
|
+
}
|
|
344
|
+
async getHistory() {
|
|
345
|
+
if (this.historyCache) {
|
|
346
|
+
return this.historyCache;
|
|
347
|
+
}
|
|
348
|
+
if (!this.provider) {
|
|
349
|
+
this.historyCache = [];
|
|
350
|
+
return this.historyCache;
|
|
351
|
+
}
|
|
352
|
+
if (this.provider.getHistoryItems) {
|
|
353
|
+
this.historyCache = await this.provider.getHistoryItems(
|
|
354
|
+
this.previousResponseId,
|
|
355
|
+
this.conversationId,
|
|
356
|
+
this.historyLimit,
|
|
357
|
+
{ isolation: this.isolation }
|
|
358
|
+
);
|
|
359
|
+
return this.historyCache;
|
|
360
|
+
}
|
|
361
|
+
const historyItemIds = await this.provider.getHistoryItemIds(
|
|
362
|
+
this.previousResponseId,
|
|
363
|
+
this.conversationId,
|
|
364
|
+
this.historyLimit,
|
|
365
|
+
{ isolation: this.isolation }
|
|
366
|
+
);
|
|
367
|
+
const items = await this.provider.getItems(historyItemIds, {
|
|
368
|
+
isolation: this.isolation
|
|
369
|
+
});
|
|
370
|
+
this.historyCache = items.filter(
|
|
371
|
+
(item) => item !== void 0
|
|
372
|
+
);
|
|
373
|
+
return this.historyCache;
|
|
374
|
+
}
|
|
375
|
+
async getInputItemsForPersistence() {
|
|
376
|
+
const items = await this.getInputItems({ resolveReferences: true });
|
|
377
|
+
return items.map((item) => toOutputItem(item, this.responseId));
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
// src/hosting/execution.ts
|
|
382
|
+
import {
|
|
383
|
+
ATTR_RESPONSES_ERROR_CODE,
|
|
384
|
+
ATTR_RESPONSES_ERROR_MESSAGE
|
|
385
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
386
|
+
function createExecution(options) {
|
|
387
|
+
const controller = new AbortController();
|
|
388
|
+
const response = createResponseObject({
|
|
389
|
+
id: options.responseId,
|
|
390
|
+
request: options.request,
|
|
391
|
+
status: options.modeFlags.background ? "queued" : "in_progress",
|
|
392
|
+
output: [],
|
|
393
|
+
createdAt: options.context.createdAt
|
|
394
|
+
});
|
|
395
|
+
const execution = {
|
|
396
|
+
responseId: options.responseId,
|
|
397
|
+
request: options.request,
|
|
398
|
+
response,
|
|
399
|
+
inputItems: options.inputItems,
|
|
400
|
+
modeFlags: options.modeFlags,
|
|
401
|
+
isolation: options.isolation,
|
|
402
|
+
controller,
|
|
403
|
+
done: Promise.resolve(response)
|
|
404
|
+
};
|
|
405
|
+
options.active.set(
|
|
406
|
+
scopedActiveKey(options.responseId, options.isolation),
|
|
407
|
+
execution
|
|
408
|
+
);
|
|
409
|
+
execution.done = options.requestSpan.run(async () => {
|
|
410
|
+
let sequenceNumber = 0;
|
|
411
|
+
if (options.modeFlags.store) {
|
|
412
|
+
await options.provider.createResponse(response, options.inputItems, {
|
|
413
|
+
historyItemIds: options.historyItemIds,
|
|
414
|
+
isolation: options.isolation
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const result = await options.handler.handle(
|
|
419
|
+
options.request,
|
|
420
|
+
options.context,
|
|
421
|
+
controller.signal
|
|
422
|
+
);
|
|
423
|
+
for await (const event of normalizeHandlerResult(result)) {
|
|
424
|
+
const normalized = withSequenceNumber(
|
|
425
|
+
normalizeEvent(event),
|
|
426
|
+
sequenceNumber
|
|
427
|
+
);
|
|
428
|
+
sequenceNumber = normalized.sequence_number + 1;
|
|
429
|
+
applyEventToExecution(execution, normalized);
|
|
430
|
+
if (options.modeFlags.store) {
|
|
431
|
+
await options.streamProvider.appendStreamEvent(
|
|
432
|
+
options.responseId,
|
|
433
|
+
normalized,
|
|
434
|
+
{ isolation: options.isolation }
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
await options.onEvent?.(normalized);
|
|
438
|
+
}
|
|
439
|
+
await completeExecutionIfNeeded(execution, options, sequenceNumber);
|
|
440
|
+
if (options.modeFlags.store) {
|
|
441
|
+
await options.provider.updateResponse(execution.response, {
|
|
442
|
+
isolation: options.isolation
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return execution.response;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
await failExecution(execution, options, error, sequenceNumber);
|
|
448
|
+
return execution.response;
|
|
449
|
+
} finally {
|
|
450
|
+
options.active.delete(
|
|
451
|
+
scopedActiveKey(options.responseId, options.isolation)
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
return execution;
|
|
456
|
+
}
|
|
457
|
+
async function completeExecutionIfNeeded(execution, options, sequenceNumber) {
|
|
458
|
+
if (isTerminalStatus(execution.response.status)) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
execution.response = {
|
|
462
|
+
...execution.response,
|
|
463
|
+
status: execution.controller.signal.aborted ? "cancelled" : "completed"
|
|
464
|
+
};
|
|
465
|
+
const terminalEvent = {
|
|
466
|
+
type: execution.controller.signal.aborted ? "response.cancelled" : "response.completed",
|
|
467
|
+
sequence_number: sequenceNumber,
|
|
468
|
+
response: execution.response
|
|
469
|
+
};
|
|
470
|
+
if (options.modeFlags.store) {
|
|
471
|
+
await options.streamProvider.appendStreamEvent(
|
|
472
|
+
options.responseId,
|
|
473
|
+
terminalEvent,
|
|
474
|
+
{ isolation: options.isolation }
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
await options.onEvent?.(terminalEvent);
|
|
478
|
+
}
|
|
479
|
+
async function failExecution(execution, options, error, sequenceNumber) {
|
|
480
|
+
const aborted = execution.controller.signal.aborted;
|
|
481
|
+
execution.response = {
|
|
482
|
+
...execution.response,
|
|
483
|
+
status: aborted ? "cancelled" : "failed",
|
|
484
|
+
error: aborted ? null : {
|
|
485
|
+
code: "server_error",
|
|
486
|
+
message: "internal server error",
|
|
487
|
+
type: "server_error"
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
options.requestSpan.recordError(
|
|
491
|
+
error,
|
|
492
|
+
aborted ? "cancelled" : "server_error"
|
|
493
|
+
);
|
|
494
|
+
options.requestSpan.span.setAttribute(
|
|
495
|
+
ATTR_RESPONSES_ERROR_CODE,
|
|
496
|
+
aborted ? "cancelled" : "server_error"
|
|
497
|
+
);
|
|
498
|
+
options.requestSpan.span.setAttribute(
|
|
499
|
+
ATTR_RESPONSES_ERROR_MESSAGE,
|
|
500
|
+
errorMessage(error)
|
|
501
|
+
);
|
|
502
|
+
const terminalEvent = {
|
|
503
|
+
type: aborted ? "response.cancelled" : "response.failed",
|
|
504
|
+
sequence_number: sequenceNumber,
|
|
505
|
+
response: execution.response
|
|
506
|
+
};
|
|
507
|
+
if (options.modeFlags.store) {
|
|
508
|
+
await options.streamProvider.appendStreamEvent(
|
|
509
|
+
options.responseId,
|
|
510
|
+
terminalEvent,
|
|
511
|
+
{ isolation: options.isolation }
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
await options.onEvent?.(terminalEvent);
|
|
515
|
+
if (options.modeFlags.store) {
|
|
516
|
+
await options.provider.updateResponse(execution.response, {
|
|
517
|
+
isolation: options.isolation
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function applyEventToExecution(execution, event) {
|
|
522
|
+
if (event.response) {
|
|
523
|
+
execution.response = event.response;
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if ((event.type === "response.output_item.done" || event.type === "response.output_item.added") && event.item) {
|
|
527
|
+
const outputIndex = typeof event.output_index === "number" ? event.output_index : execution.response.output.length;
|
|
528
|
+
const output = [...execution.response.output];
|
|
529
|
+
output[outputIndex] = event.item;
|
|
530
|
+
execution.response = { ...execution.response, output };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async function* normalizeHandlerResult(result) {
|
|
534
|
+
if (isAsyncIterable(result)) {
|
|
535
|
+
for await (const event of result) {
|
|
536
|
+
yield normalizeEvent(event);
|
|
537
|
+
}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
if (isIterable(result)) {
|
|
541
|
+
for (const event of result) {
|
|
542
|
+
yield normalizeEvent(event);
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
throw new Error("response handler must return an iterable of events");
|
|
547
|
+
}
|
|
548
|
+
function normalizeEvent(event) {
|
|
549
|
+
if (typeof event.type !== "string" || !event.type) {
|
|
550
|
+
throw new Error("response stream event must include a string type");
|
|
551
|
+
}
|
|
552
|
+
return event;
|
|
553
|
+
}
|
|
554
|
+
function withSequenceNumber(event, fallback) {
|
|
555
|
+
const sequenceNumber = typeof event.sequence_number === "number" && event.sequence_number >= 0 ? event.sequence_number : fallback;
|
|
556
|
+
return { ...event, sequence_number: sequenceNumber };
|
|
557
|
+
}
|
|
558
|
+
function isTerminalStatus(status) {
|
|
559
|
+
return ["completed", "failed", "cancelled", "incomplete"].includes(status);
|
|
560
|
+
}
|
|
561
|
+
function isAsyncIterable(value) {
|
|
562
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
563
|
+
}
|
|
564
|
+
function isIterable(value) {
|
|
565
|
+
return typeof value === "object" && value !== null && Symbol.iterator in value;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/hosting/tracing.ts
|
|
569
|
+
import {
|
|
570
|
+
startAgentServerRequestSpan
|
|
571
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
572
|
+
function startResponseRequestSpan(options) {
|
|
573
|
+
const span = startAgentServerRequestSpan({
|
|
574
|
+
config: options.config,
|
|
575
|
+
headers: (name) => options.req.header(name),
|
|
576
|
+
requestId: options.responseId,
|
|
577
|
+
operation: "invoke_agent",
|
|
578
|
+
operationName: "invoke_agent",
|
|
579
|
+
instrumentationScope: "Azure.AI.AgentServer.Responses",
|
|
580
|
+
responseId: options.responseId,
|
|
581
|
+
sessionId: options.sessionId,
|
|
582
|
+
conversationId: options.conversationId,
|
|
583
|
+
streaming: options.streaming,
|
|
584
|
+
correlationRequestId: options.req.agentServerRequestId
|
|
585
|
+
});
|
|
586
|
+
let finished = false;
|
|
587
|
+
options.res.once("finish", () => {
|
|
588
|
+
finished = true;
|
|
589
|
+
span.end();
|
|
590
|
+
});
|
|
591
|
+
options.res.once("close", () => {
|
|
592
|
+
if (finished) {
|
|
593
|
+
span.end();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
span.end(new Error("HTTP response closed before finish"));
|
|
597
|
+
});
|
|
598
|
+
return span;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/hosting/validation.ts
|
|
602
|
+
var ResponseValidationError = class extends Error {
|
|
603
|
+
constructor(message, code, param) {
|
|
604
|
+
super(message);
|
|
605
|
+
this.code = code;
|
|
606
|
+
this.param = param;
|
|
607
|
+
this.name = "ResponseValidationError";
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
function validateCreateResponseSemantics(request) {
|
|
611
|
+
const storeEnabled = request.store !== false;
|
|
612
|
+
if (request.background === true && !storeEnabled) {
|
|
613
|
+
throw new ResponseValidationError(
|
|
614
|
+
"background=true requires store=true",
|
|
615
|
+
"unsupported_parameter",
|
|
616
|
+
"background"
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
if (request.stream_options !== void 0 && request.stream !== true) {
|
|
620
|
+
throw new ResponseValidationError(
|
|
621
|
+
"stream_options requires stream=true",
|
|
622
|
+
"invalid_mode",
|
|
623
|
+
"stream"
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
const metadata = request.metadata;
|
|
627
|
+
if (isRecord(metadata)) {
|
|
628
|
+
if (Object.keys(metadata).length > 16) {
|
|
629
|
+
throw new ResponseValidationError(
|
|
630
|
+
"metadata must have at most 16 key-value pairs",
|
|
631
|
+
"invalid_request",
|
|
632
|
+
"metadata"
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
636
|
+
if (key.length > 64) {
|
|
637
|
+
throw new ResponseValidationError(
|
|
638
|
+
"metadata keys must be at most 64 characters",
|
|
639
|
+
"invalid_request",
|
|
640
|
+
"metadata"
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (typeof value === "string" && value.length > 512) {
|
|
644
|
+
throw new ResponseValidationError(
|
|
645
|
+
"metadata values must be at most 512 characters",
|
|
646
|
+
"invalid_request",
|
|
647
|
+
"metadata"
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function isRecord(value) {
|
|
654
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/hosting/routes/create-response.ts
|
|
658
|
+
function createResponseHandler(options) {
|
|
659
|
+
return async (req, res) => {
|
|
660
|
+
let request;
|
|
661
|
+
let responseId = "resp_validation";
|
|
662
|
+
try {
|
|
663
|
+
request = applyCreateResponseDefaults(normalizeCreateResponse(req.body), {
|
|
664
|
+
defaultModel: options.defaultModel
|
|
665
|
+
});
|
|
666
|
+
validateCreateResponseSemantics(request);
|
|
667
|
+
const agentReference = resolveAgentReference(request.agent_reference);
|
|
668
|
+
responseId = resolveResponseId(request, {
|
|
669
|
+
get: (name) => req.header(name)
|
|
670
|
+
});
|
|
671
|
+
const conversationId = resolveConversationId(request);
|
|
672
|
+
const previousResponseId = typeof request.previous_response_id === "string" ? request.previous_response_id : void 0;
|
|
673
|
+
if (previousResponseId) {
|
|
674
|
+
validateResponseId(previousResponseId);
|
|
675
|
+
}
|
|
676
|
+
const sessionId = resolveSessionId({
|
|
677
|
+
request,
|
|
678
|
+
envSessionId: options.config.sessionId,
|
|
679
|
+
...agentReference ? { agentReference } : {}
|
|
680
|
+
});
|
|
681
|
+
addProtectedHeaders(res, { [SESSION_ID_HEADER]: sessionId });
|
|
682
|
+
const isolation = resolveAgentIsolation2((name) => req.header(name));
|
|
683
|
+
const modeFlags = {
|
|
684
|
+
stream: request.stream === true,
|
|
685
|
+
background: request.background === true,
|
|
686
|
+
store: request.store !== false
|
|
687
|
+
};
|
|
688
|
+
const inputItems = getInputExpanded(request, responseId);
|
|
689
|
+
const historyItemIds = modeFlags.store ? await options.provider.getHistoryItemIds(
|
|
690
|
+
previousResponseId,
|
|
691
|
+
conversationId,
|
|
692
|
+
options.defaultHistoryLimit,
|
|
693
|
+
{ isolation }
|
|
694
|
+
) : [];
|
|
695
|
+
const context = new ResponseContext({
|
|
696
|
+
responseId,
|
|
697
|
+
request,
|
|
698
|
+
modeFlags,
|
|
699
|
+
sessionId,
|
|
700
|
+
provider: options.provider,
|
|
701
|
+
inputItems,
|
|
702
|
+
...previousResponseId ? { previousResponseId } : {},
|
|
703
|
+
...conversationId ? { conversationId } : {},
|
|
704
|
+
historyLimit: options.defaultHistoryLimit,
|
|
705
|
+
clientHeaders: collectClientHeaders(req.headers),
|
|
706
|
+
queryParameters: queryParameters(req),
|
|
707
|
+
isolation
|
|
708
|
+
});
|
|
709
|
+
const requestSpan = startResponseRequestSpan({
|
|
710
|
+
req,
|
|
711
|
+
res,
|
|
712
|
+
config: options.config,
|
|
713
|
+
responseId,
|
|
714
|
+
sessionId,
|
|
715
|
+
conversationId,
|
|
716
|
+
streaming: modeFlags.stream
|
|
717
|
+
});
|
|
718
|
+
const handler = options.handlerProvider();
|
|
719
|
+
const sseState = createSseEncoderState();
|
|
720
|
+
if (modeFlags.stream) {
|
|
721
|
+
startResponsesSseResponse(res);
|
|
722
|
+
}
|
|
723
|
+
const execution = createExecution({
|
|
724
|
+
request,
|
|
725
|
+
responseId,
|
|
726
|
+
inputItems,
|
|
727
|
+
historyItemIds,
|
|
728
|
+
modeFlags,
|
|
729
|
+
context,
|
|
730
|
+
handler,
|
|
731
|
+
provider: options.provider,
|
|
732
|
+
streamProvider: options.streamProvider,
|
|
733
|
+
active: options.active,
|
|
734
|
+
isolation,
|
|
735
|
+
requestSpan,
|
|
736
|
+
onEvent: modeFlags.stream ? (event) => {
|
|
737
|
+
res.write(encodeSseEvent(event, sseState));
|
|
738
|
+
} : void 0
|
|
739
|
+
});
|
|
740
|
+
if (modeFlags.background && !modeFlags.stream) {
|
|
741
|
+
void execution.done.catch((error) => {
|
|
742
|
+
options.logger.error("Background response execution failed", {
|
|
743
|
+
responseId,
|
|
744
|
+
error: formatError(error)
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
res.status(200).json(execution.response);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (modeFlags.stream) {
|
|
751
|
+
try {
|
|
752
|
+
await execution.done;
|
|
753
|
+
} finally {
|
|
754
|
+
res.end();
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const response = await execution.done;
|
|
759
|
+
res.status(200).json(response);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
options.logger.error("Failed to create response", {
|
|
762
|
+
responseId,
|
|
763
|
+
error: formatError(error)
|
|
764
|
+
});
|
|
765
|
+
const validationError = error instanceof ResponseValidationError ? error : void 0;
|
|
766
|
+
res.status(400).json(
|
|
767
|
+
createErrorBody4(
|
|
768
|
+
validationError?.code ?? "invalid_request",
|
|
769
|
+
errorMessage(error),
|
|
770
|
+
{
|
|
771
|
+
type: "invalid_request_error",
|
|
772
|
+
requestId: req.agentServerRequestId,
|
|
773
|
+
additionalInfo: validationError?.param ? { param: validationError.param } : void 0
|
|
774
|
+
}
|
|
775
|
+
)
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/hosting/routes/delete-response.ts
|
|
782
|
+
import {
|
|
783
|
+
createErrorBody as createErrorBody5,
|
|
784
|
+
resolveAgentIsolation as resolveAgentIsolation3
|
|
785
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
786
|
+
function deleteResponseHandler(options) {
|
|
787
|
+
return async (req, res) => {
|
|
788
|
+
const responseId = routeResponseId(req);
|
|
789
|
+
if (!validateRouteResponseId(responseId, res)) {
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const isolation = resolveAgentIsolation3((name) => req.header(name));
|
|
793
|
+
const activeExecution = options.active.get(
|
|
794
|
+
scopedActiveKey(responseId, isolation)
|
|
795
|
+
);
|
|
796
|
+
if (activeExecution?.modeFlags.store === false) {
|
|
797
|
+
res.status(404).json(createErrorBody5("not_found", "Response not found"));
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
if (activeExecution?.modeFlags.background && ["queued", "in_progress"].includes(activeExecution.response.status)) {
|
|
801
|
+
res.status(400).json(
|
|
802
|
+
createErrorBody5(
|
|
803
|
+
"invalid_request",
|
|
804
|
+
"Cannot delete an in-flight response."
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
activeExecution?.controller.abort();
|
|
810
|
+
const deleted = await options.provider.deleteResponse(responseId, {
|
|
811
|
+
isolation
|
|
812
|
+
});
|
|
813
|
+
await options.streamProvider.deleteStreamEvents(responseId, { isolation });
|
|
814
|
+
if (!deleted && !activeExecution) {
|
|
815
|
+
res.status(404).json(createErrorBody5("not_found", "Response not found"));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
options.active.delete(scopedActiveKey(responseId, isolation));
|
|
819
|
+
res.status(200).json({ id: responseId, object: "response", deleted: true });
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/hosting/routes/get-response.ts
|
|
824
|
+
import {
|
|
825
|
+
createErrorBody as createErrorBody6,
|
|
826
|
+
resolveAgentIsolation as resolveAgentIsolation4
|
|
827
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
828
|
+
function getResponseHandler(options) {
|
|
829
|
+
return async (req, res) => {
|
|
830
|
+
const responseId = routeResponseId(req);
|
|
831
|
+
if (!validateRouteResponseId(responseId, res)) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
const isolation = resolveAgentIsolation4((name) => req.header(name));
|
|
835
|
+
const activeExecution = options.active.get(
|
|
836
|
+
scopedActiveKey(responseId, isolation)
|
|
837
|
+
);
|
|
838
|
+
const streamReplay = String(req.query.stream ?? "false").toLowerCase() === "true";
|
|
839
|
+
if (streamReplay) {
|
|
840
|
+
const startingAfter = parseStartingAfter(req, res);
|
|
841
|
+
if (startingAfter === false) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
await handleStreamReplay({
|
|
845
|
+
activeExecution,
|
|
846
|
+
isolation,
|
|
847
|
+
routeOptions: options,
|
|
848
|
+
responseId,
|
|
849
|
+
res,
|
|
850
|
+
startingAfter
|
|
851
|
+
});
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const response = activeExecution?.response ?? await options.provider.getResponse(responseId, { isolation });
|
|
855
|
+
if (!response || activeExecution?.modeFlags.store === false) {
|
|
856
|
+
res.status(404).json(createErrorBody6("not_found", "Response not found"));
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
res.status(200).json(response);
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
async function handleStreamReplay(options) {
|
|
863
|
+
const response = options.activeExecution?.response ?? await options.routeOptions.provider.getResponse(options.responseId, {
|
|
864
|
+
isolation: options.isolation
|
|
865
|
+
});
|
|
866
|
+
if (!response) {
|
|
867
|
+
options.res.status(404).json(createErrorBody6("not_found", "Response not found"));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const flags = options.activeExecution?.modeFlags ?? modeFlagsFromResponse(response);
|
|
871
|
+
if (!flags.store) {
|
|
872
|
+
options.res.status(404).json(createErrorBody6("not_found", "Response not found"));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (!flags.background) {
|
|
876
|
+
options.res.status(400).json(
|
|
877
|
+
createErrorBody6(
|
|
878
|
+
"invalid_mode",
|
|
879
|
+
"This response cannot be streamed because it was not created with background=true."
|
|
880
|
+
)
|
|
881
|
+
);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
if (!flags.stream) {
|
|
885
|
+
options.res.status(400).json(
|
|
886
|
+
createErrorBody6(
|
|
887
|
+
"invalid_mode",
|
|
888
|
+
"This response cannot be streamed because it was not created with stream=true."
|
|
889
|
+
)
|
|
890
|
+
);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const storedEvents = await options.routeOptions.streamProvider.getStreamEvents(
|
|
894
|
+
options.responseId,
|
|
895
|
+
{ isolation: options.isolation }
|
|
896
|
+
) ?? [];
|
|
897
|
+
const events = storedEvents.map((event, index) => ({
|
|
898
|
+
...event,
|
|
899
|
+
sequence_number: typeof event.sequence_number === "number" ? event.sequence_number : index
|
|
900
|
+
})).filter(
|
|
901
|
+
(event) => options.startingAfter === void 0 || event.sequence_number > options.startingAfter
|
|
902
|
+
);
|
|
903
|
+
startResponsesSseResponse(options.res);
|
|
904
|
+
const sseState = createSseEncoderState();
|
|
905
|
+
for (const event of events) {
|
|
906
|
+
options.res.write(encodeSseEvent(event, sseState));
|
|
907
|
+
}
|
|
908
|
+
options.res.end();
|
|
909
|
+
}
|
|
910
|
+
function parseStartingAfter(req, res) {
|
|
911
|
+
const value = req.query.starting_after;
|
|
912
|
+
if (value === void 0) {
|
|
913
|
+
return void 0;
|
|
914
|
+
}
|
|
915
|
+
if (Array.isArray(value) || typeof value !== "string" || !value.trim()) {
|
|
916
|
+
res.status(400).json(
|
|
917
|
+
createErrorBody6("invalid_request", "starting_after must be an integer", {
|
|
918
|
+
type: "invalid_request_error",
|
|
919
|
+
additionalInfo: { param: "starting_after" }
|
|
920
|
+
})
|
|
921
|
+
);
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
const parsed = Number.parseInt(value, 10);
|
|
925
|
+
if (!Number.isInteger(parsed) || String(parsed) !== value.trim()) {
|
|
926
|
+
res.status(400).json(
|
|
927
|
+
createErrorBody6("invalid_request", "starting_after must be an integer", {
|
|
928
|
+
type: "invalid_request_error",
|
|
929
|
+
additionalInfo: { param: "starting_after" }
|
|
930
|
+
})
|
|
931
|
+
);
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
return parsed;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// src/hosting/routes/list-input-items.ts
|
|
938
|
+
import {
|
|
939
|
+
createErrorBody as createErrorBody7,
|
|
940
|
+
resolveAgentIsolation as resolveAgentIsolation5
|
|
941
|
+
} from "@cuylabs/agent-foundry-agentserver-core";
|
|
942
|
+
function listInputItemsHandler(options) {
|
|
943
|
+
return async (req, res) => {
|
|
944
|
+
const responseId = routeResponseId(req);
|
|
945
|
+
if (!validateRouteResponseId(responseId, res)) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const isolation = resolveAgentIsolation5((name) => req.header(name));
|
|
949
|
+
const limit = parseLimit(req.query.limit);
|
|
950
|
+
const items = await options.provider.getInputItems(responseId, {
|
|
951
|
+
after: queryString(req.query.after),
|
|
952
|
+
ascending: String(req.query.order ?? "desc").toLowerCase() === "asc",
|
|
953
|
+
before: queryString(req.query.before),
|
|
954
|
+
isolation,
|
|
955
|
+
limit: Math.min(limit + 1, 100)
|
|
956
|
+
});
|
|
957
|
+
if (!items) {
|
|
958
|
+
res.status(404).json(createErrorBody7("not_found", "Response not found"));
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const page = items.slice(0, limit);
|
|
962
|
+
res.status(200).json({
|
|
963
|
+
object: "list",
|
|
964
|
+
data: page,
|
|
965
|
+
first_id: page[0]?.id ?? null,
|
|
966
|
+
last_id: page[page.length - 1]?.id ?? null,
|
|
967
|
+
has_more: items.length > limit
|
|
968
|
+
});
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/hosting/routes.ts
|
|
973
|
+
function registerResponsesRoutes(app, options) {
|
|
974
|
+
app.get("/", (_req, res) => {
|
|
975
|
+
res.json({
|
|
976
|
+
app: options.appName,
|
|
977
|
+
host: "foundry-responses",
|
|
978
|
+
protocol: "responses",
|
|
979
|
+
protocolVersion: "1.0.0",
|
|
980
|
+
endpoint: "/responses",
|
|
981
|
+
readiness: "/readiness",
|
|
982
|
+
liveness: "/healthz",
|
|
983
|
+
openapi: "/responses/docs/openapi.json"
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
app.get("/healthz", healthzHandler);
|
|
987
|
+
app.get("/readiness", readinessHandler);
|
|
988
|
+
app.get("/readyz", readinessHandler);
|
|
989
|
+
app.get("/responses/docs/openapi.json", (_req, res) => {
|
|
990
|
+
const handler = options.handlerProvider();
|
|
991
|
+
res.json(
|
|
992
|
+
handler.getOpenApi?.() ?? buildResponsesOpenApiSpec({ appName: options.appName })
|
|
993
|
+
);
|
|
994
|
+
});
|
|
995
|
+
app.post("/responses", createResponseHandler(options));
|
|
996
|
+
app.get("/responses/:responseId", getResponseHandler(options));
|
|
997
|
+
app.delete("/responses/:responseId", deleteResponseHandler(options));
|
|
998
|
+
app.post("/responses/:responseId/cancel", cancelResponseHandler(options));
|
|
999
|
+
app.get("/responses/:responseId/input_items", listInputItemsHandler(options));
|
|
1000
|
+
app.all("/responses", methodNotAllowed(["POST"]));
|
|
1001
|
+
app.all("/responses/docs/openapi.json", methodNotAllowed(["GET"]));
|
|
1002
|
+
app.all("/responses/:responseId/cancel", methodNotAllowed(["POST"]));
|
|
1003
|
+
app.all("/responses/:responseId/input_items", methodNotAllowed(["GET"]));
|
|
1004
|
+
app.all("/responses/:responseId", methodNotAllowed(["GET", "DELETE"]));
|
|
1005
|
+
app.all("/readiness", methodNotAllowed(["GET"]));
|
|
1006
|
+
app.all("/readyz", methodNotAllowed(["GET"]));
|
|
1007
|
+
app.all("/healthz", methodNotAllowed(["GET"]));
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// src/host.ts
|
|
1011
|
+
var DEFAULT_HOST = "0.0.0.0";
|
|
1012
|
+
var DEFAULT_BODY_LIMIT = "1mb";
|
|
1013
|
+
var DEFAULT_HISTORY_LIMIT = 100;
|
|
1014
|
+
function createResponsesApp(options) {
|
|
1015
|
+
const logger = options.logger ?? defaultAgentServerLogger("responses");
|
|
1016
|
+
const config = resolveAgentServerConfig({
|
|
1017
|
+
port: options.port,
|
|
1018
|
+
gracefulShutdownTimeoutSeconds: options.gracefulShutdownTimeoutSeconds
|
|
1019
|
+
});
|
|
1020
|
+
const appName = options.appName ?? "foundry-responses-host";
|
|
1021
|
+
const trustProxy = options.trustProxy ?? 1;
|
|
1022
|
+
const bodyLimit = options.bodyLimit ?? DEFAULT_BODY_LIMIT;
|
|
1023
|
+
const defaultHistoryLimit = resolveDefaultHistoryLimit(
|
|
1024
|
+
options.defaultFetchHistoryCount
|
|
1025
|
+
);
|
|
1026
|
+
const requestLogging = options.requestLogging !== false;
|
|
1027
|
+
const provider = options.store ?? new InMemoryResponseProvider();
|
|
1028
|
+
const streamProvider = supportsStreamProvider(provider) ? provider : new InMemoryResponseProvider();
|
|
1029
|
+
const active = /* @__PURE__ */ new Map();
|
|
1030
|
+
const platformServer = responsesPlatformServerHeader();
|
|
1031
|
+
const observabilityReady = options.configureObservability === false ? Promise.resolve(void 0) : configureAgentServerObservability({ config, logger });
|
|
1032
|
+
const app = express();
|
|
1033
|
+
configureExpressAgentServerApp(app, { trustProxy });
|
|
1034
|
+
app.use(agentServerRequestHeadersMiddleware({ platformServer }));
|
|
1035
|
+
app.use(express.json({ type: "*/*", limit: bodyLimit }));
|
|
1036
|
+
app.use(jsonParseErrorMiddleware);
|
|
1037
|
+
app.use(async (_req, _res, next) => {
|
|
1038
|
+
try {
|
|
1039
|
+
await observabilityReady;
|
|
1040
|
+
next();
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
logger.warn("Foundry Agent Server observability initialization failed", {
|
|
1043
|
+
error: formatError2(error)
|
|
1044
|
+
});
|
|
1045
|
+
next();
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
if (requestLogging) {
|
|
1049
|
+
app.use(agentServerLoggingMiddleware(logger));
|
|
1050
|
+
}
|
|
1051
|
+
registerResponsesRoutes(app, {
|
|
1052
|
+
active,
|
|
1053
|
+
appName,
|
|
1054
|
+
config,
|
|
1055
|
+
defaultHistoryLimit,
|
|
1056
|
+
defaultModel: options.defaultModel,
|
|
1057
|
+
handlerProvider: makeHandlerProvider(options.handler),
|
|
1058
|
+
logger,
|
|
1059
|
+
provider,
|
|
1060
|
+
streamProvider
|
|
1061
|
+
});
|
|
1062
|
+
app.use(notFoundMiddleware());
|
|
1063
|
+
app.use(errorMiddleware(logger));
|
|
1064
|
+
return app;
|
|
1065
|
+
}
|
|
1066
|
+
async function runResponsesServer(options) {
|
|
1067
|
+
const app = createResponsesApp(options);
|
|
1068
|
+
const config = resolveAgentServerConfig({
|
|
1069
|
+
port: options.port,
|
|
1070
|
+
gracefulShutdownTimeoutSeconds: options.gracefulShutdownTimeoutSeconds
|
|
1071
|
+
});
|
|
1072
|
+
const port = config.port;
|
|
1073
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
1074
|
+
const logger = options.logger ?? defaultAgentServerLogger("responses");
|
|
1075
|
+
const appName = options.appName ?? "foundry-responses-host";
|
|
1076
|
+
const platformServer = responsesPlatformServerHeader();
|
|
1077
|
+
const server = await new Promise((resolve, reject) => {
|
|
1078
|
+
const httpServer = app.listen(port, host, () => {
|
|
1079
|
+
logAgentServerStartupConfiguration(logger, config, platformServer);
|
|
1080
|
+
logger.info(`${appName} listening on http://${host}:${port}/responses`);
|
|
1081
|
+
resolve(httpServer);
|
|
1082
|
+
}).on("error", reject);
|
|
1083
|
+
});
|
|
1084
|
+
return {
|
|
1085
|
+
app,
|
|
1086
|
+
server,
|
|
1087
|
+
port,
|
|
1088
|
+
host,
|
|
1089
|
+
async close() {
|
|
1090
|
+
await closeServerWithTimeout(
|
|
1091
|
+
server,
|
|
1092
|
+
config.gracefulShutdownTimeoutSeconds
|
|
1093
|
+
);
|
|
1094
|
+
const handler = options.handler;
|
|
1095
|
+
if (typeof handler === "object" && "close" in handler) {
|
|
1096
|
+
await handler.close?.();
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function resolveDefaultHistoryLimit(value) {
|
|
1102
|
+
if (value === void 0) {
|
|
1103
|
+
return DEFAULT_HISTORY_LIMIT;
|
|
1104
|
+
}
|
|
1105
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1106
|
+
throw new Error("defaultFetchHistoryCount must be a positive integer");
|
|
1107
|
+
}
|
|
1108
|
+
return value;
|
|
1109
|
+
}
|
|
1110
|
+
function responsesPlatformServerHeader() {
|
|
1111
|
+
return buildPlatformServerHeader([
|
|
1112
|
+
{
|
|
1113
|
+
name: "azure-ai-agentserver-core",
|
|
1114
|
+
version: AGENTSERVER_CORE_PACKAGE_VERSION
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
name: "azure-ai-agentserver-responses",
|
|
1118
|
+
version: AGENTSERVER_RESPONSES_PACKAGE_VERSION
|
|
1119
|
+
}
|
|
1120
|
+
]);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/types.ts
|
|
1124
|
+
var ResponsesHandler = class {
|
|
1125
|
+
};
|
|
1126
|
+
export {
|
|
1127
|
+
AGENTSERVER_RESPONSES_PACKAGE_VERSION,
|
|
1128
|
+
FoundryStorageError,
|
|
1129
|
+
FoundryStorageNotFoundError,
|
|
1130
|
+
FoundryStorageProvider,
|
|
1131
|
+
FoundryStorageSettings,
|
|
1132
|
+
InMemoryResponseProvider,
|
|
1133
|
+
RESPONSE_ID_HEADER,
|
|
1134
|
+
ResponseContext,
|
|
1135
|
+
ResponsesHandler,
|
|
1136
|
+
SESSION_ID_HEADER,
|
|
1137
|
+
TextResponse,
|
|
1138
|
+
buildResponsesOpenApiSpec,
|
|
1139
|
+
createResponseObject,
|
|
1140
|
+
createResponsesApp,
|
|
1141
|
+
createSseEncoderState,
|
|
1142
|
+
deriveSessionId,
|
|
1143
|
+
encodeKeepAliveComment,
|
|
1144
|
+
encodeSseEvent,
|
|
1145
|
+
extractPartitionKey,
|
|
1146
|
+
extractTextFromItems,
|
|
1147
|
+
getInputExpanded,
|
|
1148
|
+
isValidId,
|
|
1149
|
+
newId,
|
|
1150
|
+
newMessageItemId,
|
|
1151
|
+
newOutputMessageItemId,
|
|
1152
|
+
newResponseId,
|
|
1153
|
+
normalizeCreateResponse,
|
|
1154
|
+
normalizeResponseInput,
|
|
1155
|
+
resolveAgentReference,
|
|
1156
|
+
resolveConversationId,
|
|
1157
|
+
resolveResponseId,
|
|
1158
|
+
resolveSessionId,
|
|
1159
|
+
runResponsesServer,
|
|
1160
|
+
startResponsesSseResponse,
|
|
1161
|
+
toOutputItem,
|
|
1162
|
+
validateResponseId
|
|
1163
|
+
};
|