@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,513 @@
1
+ // src/streaming/sse.ts
2
+ function startResponsesSseResponse(res) {
3
+ res.status(200);
4
+ res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
5
+ res.setHeader("Cache-Control", "no-cache");
6
+ res.setHeader("Connection", "keep-alive");
7
+ res.setHeader("X-Accel-Buffering", "no");
8
+ res.flushHeaders?.();
9
+ }
10
+ function createSseEncoderState() {
11
+ return { sequenceNumber: 0 };
12
+ }
13
+ function encodeSseEvent(event, state = createSseEncoderState()) {
14
+ if (!event.type) {
15
+ throw new Error("SSE event must include a non-empty type");
16
+ }
17
+ const eventType = event.type.replaceAll("\n", "").replaceAll("\r", "");
18
+ const sequenceNumber = typeof event.sequence_number === "number" && event.sequence_number >= 0 ? event.sequence_number : state.sequenceNumber;
19
+ state.sequenceNumber = Math.max(state.sequenceNumber, sequenceNumber + 1);
20
+ const payload = {
21
+ ...event,
22
+ type: eventType,
23
+ sequence_number: sequenceNumber
24
+ };
25
+ return `event: ${eventType}
26
+ data: ${JSON.stringify(payload)}
27
+
28
+ `;
29
+ }
30
+ function encodeKeepAliveComment(comment = "keep-alive") {
31
+ return `: ${comment}
32
+
33
+ `;
34
+ }
35
+
36
+ // src/ids.ts
37
+ import { randomBytes } from "crypto";
38
+ var PARTITION_KEY_HEX_LENGTH = 16;
39
+ var PARTITION_KEY_SUFFIX = "00";
40
+ var PARTITION_KEY_TOTAL_LENGTH = PARTITION_KEY_HEX_LENGTH + PARTITION_KEY_SUFFIX.length;
41
+ var ENTROPY_LENGTH = 32;
42
+ var NEW_FORMAT_BODY_LENGTH = PARTITION_KEY_TOTAL_LENGTH + ENTROPY_LENGTH;
43
+ var LEGACY_BODY_LENGTH = 48;
44
+ var LEGACY_PARTITION_KEY_LENGTH = 16;
45
+ function newId(prefix, partitionKeyHint = "") {
46
+ if (!prefix) {
47
+ throw new Error("prefix must not be empty");
48
+ }
49
+ const extracted = tryExtractPartitionKey(partitionKeyHint);
50
+ const partitionKey = extracted ? normalizePartitionKey(extracted) : generatePartitionKey();
51
+ return `${prefix}_${partitionKey}${generateEntropy()}`;
52
+ }
53
+ function newResponseId(partitionKeyHint = "") {
54
+ return newId("caresp", partitionKeyHint);
55
+ }
56
+ function newMessageItemId(partitionKeyHint = "") {
57
+ return newId("msg", partitionKeyHint);
58
+ }
59
+ function newOutputMessageItemId(partitionKeyHint = "") {
60
+ return newId("omsg", partitionKeyHint);
61
+ }
62
+ function extractPartitionKey(idValue) {
63
+ const partitionKey = tryExtractPartitionKey(idValue);
64
+ if (partitionKey) {
65
+ return partitionKey;
66
+ }
67
+ if (!idValue) {
68
+ throw new Error("ID must not be null or empty.");
69
+ }
70
+ if (!idValue.includes("_")) {
71
+ throw new Error(`ID '${idValue}' has no '_' delimiter.`);
72
+ }
73
+ throw new Error(`ID '${idValue}' has unexpected body length.`);
74
+ }
75
+ function isValidId(idValue, allowedPrefixes) {
76
+ if (!idValue) {
77
+ return { valid: false, error: "ID must not be null or empty." };
78
+ }
79
+ const delimiterIndex = idValue.indexOf("_");
80
+ if (delimiterIndex < 0) {
81
+ return { valid: false, error: `ID '${idValue}' has no '_' delimiter.` };
82
+ }
83
+ const prefix = idValue.slice(0, delimiterIndex);
84
+ if (!prefix) {
85
+ return { valid: false, error: "ID has an empty prefix." };
86
+ }
87
+ const body = idValue.slice(delimiterIndex + 1);
88
+ if (body.length !== NEW_FORMAT_BODY_LENGTH && body.length !== LEGACY_BODY_LENGTH) {
89
+ return {
90
+ valid: false,
91
+ error: `ID '${idValue}' has unexpected body length ${body.length} (expected ${NEW_FORMAT_BODY_LENGTH} or ${LEGACY_BODY_LENGTH}).`
92
+ };
93
+ }
94
+ if (allowedPrefixes && !allowedPrefixes.includes(prefix)) {
95
+ return {
96
+ valid: false,
97
+ error: `ID prefix '${prefix}' is not in the allowed set [${allowedPrefixes.join(", ")}].`
98
+ };
99
+ }
100
+ if (!/^[A-Za-z0-9]+$/.test(body)) {
101
+ return {
102
+ valid: false,
103
+ error: `ID '${idValue}' body must be alphanumeric.`
104
+ };
105
+ }
106
+ return { valid: true };
107
+ }
108
+ function normalizePartitionKey(partitionKey) {
109
+ if (partitionKey.length === LEGACY_PARTITION_KEY_LENGTH) {
110
+ return `${partitionKey}${PARTITION_KEY_SUFFIX}`;
111
+ }
112
+ return partitionKey;
113
+ }
114
+ function generatePartitionKey() {
115
+ return `${randomBytes(8).toString("hex")}${PARTITION_KEY_SUFFIX}`;
116
+ }
117
+ function generateEntropy() {
118
+ const chars = [];
119
+ while (chars.length < ENTROPY_LENGTH) {
120
+ const candidate = randomBytes(48).toString("base64");
121
+ for (const char of candidate) {
122
+ if (/^[A-Za-z0-9]$/.test(char)) {
123
+ chars.push(char);
124
+ if (chars.length >= ENTROPY_LENGTH) {
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ return chars.join("");
131
+ }
132
+ function tryExtractPartitionKey(idValue) {
133
+ if (!idValue) {
134
+ return void 0;
135
+ }
136
+ const delimiterIndex = idValue.indexOf("_");
137
+ if (delimiterIndex < 0) {
138
+ return void 0;
139
+ }
140
+ const body = idValue.slice(delimiterIndex + 1);
141
+ if (body.length === NEW_FORMAT_BODY_LENGTH) {
142
+ return body.slice(0, PARTITION_KEY_TOTAL_LENGTH);
143
+ }
144
+ if (body.length === LEGACY_BODY_LENGTH) {
145
+ return body.slice(-LEGACY_PARTITION_KEY_LENGTH);
146
+ }
147
+ return void 0;
148
+ }
149
+
150
+ // src/model-helpers.ts
151
+ import { createHash, randomBytes as randomBytes2 } from "crypto";
152
+ var RESPONSE_ID_HEADER = "x-agent-response-id";
153
+ var SESSION_ID_HEADER = "x-agent-session-id";
154
+ var DEFAULT_AGENT_REFERENCE_NAME = "server-default-agent";
155
+ var SESSION_ID_LENGTH = 63;
156
+ function normalizeCreateResponse(value) {
157
+ if (value === void 0 || value === null) {
158
+ return {};
159
+ }
160
+ if (!isRecord(value)) {
161
+ throw new Error("request body must be a JSON object");
162
+ }
163
+ return { ...value };
164
+ }
165
+ function applyCreateResponseDefaults(request, defaults) {
166
+ const model = typeof request.model === "string" ? request.model.trim() : void 0;
167
+ if (model) {
168
+ return { ...request, model };
169
+ }
170
+ const defaultModel = defaults.defaultModel?.trim();
171
+ return defaultModel ? { ...request, model: defaultModel } : { ...request };
172
+ }
173
+ function resolveConversationId(request) {
174
+ const conversation = request.conversation;
175
+ if (typeof conversation === "string") {
176
+ return conversation || void 0;
177
+ }
178
+ if (isRecord(conversation) && typeof conversation.id === "string") {
179
+ return conversation.id || void 0;
180
+ }
181
+ return void 0;
182
+ }
183
+ function resolveResponseId(request, headers) {
184
+ const headerValue = headers.get(RESPONSE_ID_HEADER)?.trim();
185
+ const explicit = typeof request.response_id === "string" ? request.response_id.trim() : "";
186
+ const responseId = headerValue || explicit || newResponseId(
187
+ request.previous_response_id ?? resolveConversationId(request) ?? ""
188
+ );
189
+ validateResponseId(responseId);
190
+ return responseId;
191
+ }
192
+ function validateResponseId(responseId) {
193
+ const validation = isValidId(responseId, ["caresp"]);
194
+ if (!validation.valid) {
195
+ throw new Error(
196
+ "response_id must be in format caresp_<18-char partition key><32-char alphanumeric entropy>"
197
+ );
198
+ }
199
+ }
200
+ function resolveAgentReference(value) {
201
+ if (value === void 0 || value === null) {
202
+ return void 0;
203
+ }
204
+ if (!isRecord(value)) {
205
+ throw new Error("agent_reference must be an object");
206
+ }
207
+ if (value.type !== void 0 && value.type !== "agent_reference") {
208
+ throw new Error("agent_reference.type must be 'agent_reference'");
209
+ }
210
+ if (typeof value.name !== "string" || !value.name.trim()) {
211
+ throw new Error("agent_reference.name must be a non-empty string");
212
+ }
213
+ return {
214
+ type: "agent_reference",
215
+ name: value.name.trim(),
216
+ ...typeof value.version === "string" ? { version: value.version } : {},
217
+ ...typeof value.id === "string" ? { id: value.id } : {}
218
+ };
219
+ }
220
+ function resolveSessionId(options) {
221
+ const requestSessionId = typeof options.request.agent_session_id === "string" ? options.request.agent_session_id.trim() : "";
222
+ if (requestSessionId) {
223
+ return requestSessionId;
224
+ }
225
+ if (options.envSessionId?.trim()) {
226
+ return options.envSessionId.trim();
227
+ }
228
+ return deriveSessionId({
229
+ conversationId: resolveConversationId(options.request),
230
+ previousResponseId: typeof options.request.previous_response_id === "string" ? options.request.previous_response_id : void 0,
231
+ agentReference: options.agentReference
232
+ });
233
+ }
234
+ function deriveSessionId(options) {
235
+ const partitionSource = options.conversationId ?? options.previousResponseId;
236
+ if (partitionSource) {
237
+ let partitionHint;
238
+ try {
239
+ partitionHint = extractPartitionKey(partitionSource);
240
+ } catch {
241
+ partitionHint = partitionSource;
242
+ }
243
+ const agentName = options.agentReference?.name ?? DEFAULT_AGENT_REFERENCE_NAME;
244
+ const agentVersion = options.agentReference?.version ?? "";
245
+ return computeHexHash(`${agentName}:${agentVersion}:${partitionHint}`);
246
+ }
247
+ return randomBytes2(48).toString("hex").slice(0, SESSION_ID_LENGTH);
248
+ }
249
+ function getInputExpanded(request, responseId) {
250
+ return normalizeResponseInput(request.input).map(
251
+ (item) => toOutputItem(item, responseId)
252
+ );
253
+ }
254
+ function normalizeResponseInput(input) {
255
+ if (input === void 0 || input === null) {
256
+ return [];
257
+ }
258
+ if (typeof input === "string") {
259
+ return [
260
+ {
261
+ type: "message",
262
+ role: "user",
263
+ content: [{ type: "input_text", text: input }]
264
+ }
265
+ ];
266
+ }
267
+ if (Array.isArray(input)) {
268
+ return input;
269
+ }
270
+ return [input];
271
+ }
272
+ function toOutputItem(item, partitionHint) {
273
+ if (isItemReference(item)) {
274
+ return { id: item.id, type: "item_reference" };
275
+ }
276
+ if (isMessage(item)) {
277
+ const role = item.role;
278
+ const id = typeof item.id === "string" && item.id ? item.id : role === "assistant" ? newOutputMessageItemId(partitionHint) : newMessageItemId(partitionHint);
279
+ return {
280
+ id,
281
+ type: "message",
282
+ role,
283
+ status: "completed",
284
+ content: normalizeMessageContent(item.content, role)
285
+ };
286
+ }
287
+ if (isRecord(item)) {
288
+ const id = typeof item.id === "string" && item.id ? item.id : newMessageItemId(partitionHint);
289
+ return { id, ...item, type: String(item.type ?? "item") };
290
+ }
291
+ return { id: newMessageItemId(partitionHint), type: "item", value: item };
292
+ }
293
+ function extractTextFromItems(items) {
294
+ const texts = [];
295
+ for (const item of items) {
296
+ if (item.type !== "message") {
297
+ continue;
298
+ }
299
+ for (const part of item.content ?? []) {
300
+ if ((part.type === "input_text" || part.type === "output_text") && typeof part.text === "string") {
301
+ texts.push(part.text);
302
+ }
303
+ }
304
+ }
305
+ return texts.join("\n");
306
+ }
307
+ function createResponseObject(options) {
308
+ const conversationId = resolveConversationId(options.request);
309
+ return stripUndefined({
310
+ id: options.id,
311
+ object: "response",
312
+ created_at: options.createdAt ?? Math.floor(Date.now() / 1e3),
313
+ status: options.status,
314
+ model: typeof options.request.model === "string" ? options.request.model : void 0,
315
+ stream: options.request.stream === true ? true : void 0,
316
+ background: options.request.background === true ? true : void 0,
317
+ store: options.request.store === false ? false : void 0,
318
+ output: options.output ?? [],
319
+ error: options.error ?? null,
320
+ previous_response_id: typeof options.request.previous_response_id === "string" ? options.request.previous_response_id : null,
321
+ conversation: conversationId ? { id: conversationId } : void 0,
322
+ metadata: isRecord(options.request.metadata) || options.request.metadata === null ? options.request.metadata : void 0
323
+ });
324
+ }
325
+ function mergeResponseSnapshot(existing, update) {
326
+ return stripUndefined({
327
+ ...existing,
328
+ ...update,
329
+ output: update.output ?? existing.output
330
+ });
331
+ }
332
+ function normalizeMessageContent(content, role) {
333
+ if (typeof content === "string") {
334
+ return [
335
+ {
336
+ type: role === "assistant" ? "output_text" : "input_text",
337
+ text: content
338
+ }
339
+ ];
340
+ }
341
+ if (!Array.isArray(content)) {
342
+ return [];
343
+ }
344
+ return content.map((part) => {
345
+ if (isRecord(part)) {
346
+ return part;
347
+ }
348
+ return { type: "input_text", text: String(part) };
349
+ });
350
+ }
351
+ function computeHexHash(value) {
352
+ return createHash("sha256").update(value, "utf8").digest("hex").slice(0, 63);
353
+ }
354
+ function isMessage(value) {
355
+ return isRecord(value) && value.type === "message" && typeof value.role === "string" && (typeof value.content === "string" || Array.isArray(value.content));
356
+ }
357
+ function isItemReference(value) {
358
+ return isRecord(value) && value.type === "item_reference" && typeof value.id === "string";
359
+ }
360
+ function isRecord(value) {
361
+ return typeof value === "object" && value !== null && !Array.isArray(value);
362
+ }
363
+ function stripUndefined(value) {
364
+ const result = {};
365
+ for (const [key, entry] of Object.entries(value)) {
366
+ if (entry !== void 0) {
367
+ result[key] = entry;
368
+ }
369
+ }
370
+ return result;
371
+ }
372
+
373
+ // src/streaming/text-response.ts
374
+ var TextResponse = class {
375
+ constructor(context, request, options) {
376
+ this.context = context;
377
+ this.request = request;
378
+ this.options = options;
379
+ }
380
+ [Symbol.asyncIterator]() {
381
+ return this.generate();
382
+ }
383
+ async *generate() {
384
+ let response = createResponseObject({
385
+ id: this.context.responseId,
386
+ request: this.request,
387
+ status: "queued",
388
+ createdAt: this.context.createdAt
389
+ });
390
+ this.options.configure?.(response);
391
+ yield { type: "response.created", response };
392
+ response = mergeResponseSnapshot(response, { status: "in_progress" });
393
+ yield { type: "response.in_progress", response };
394
+ const outputIndex = 0;
395
+ const contentIndex = 0;
396
+ const textPart = {
397
+ type: "output_text",
398
+ text: "",
399
+ annotations: []
400
+ };
401
+ const message = {
402
+ id: newOutputMessageItemId(this.context.responseId),
403
+ type: "message",
404
+ status: "in_progress",
405
+ role: "assistant",
406
+ content: [textPart]
407
+ };
408
+ yield {
409
+ type: "response.output_item.added",
410
+ output_index: outputIndex,
411
+ item: message
412
+ };
413
+ yield {
414
+ type: "response.content_part.added",
415
+ output_index: outputIndex,
416
+ content_index: contentIndex,
417
+ item_id: message.id,
418
+ part: textPart
419
+ };
420
+ let accumulated = "";
421
+ for await (const chunk of iterateTextSource(this.options.text)) {
422
+ if (!chunk) {
423
+ continue;
424
+ }
425
+ accumulated += chunk;
426
+ yield {
427
+ type: "response.output_text.delta",
428
+ output_index: outputIndex,
429
+ content_index: contentIndex,
430
+ item_id: message.id,
431
+ delta: chunk
432
+ };
433
+ }
434
+ textPart.text = accumulated;
435
+ message.status = "completed";
436
+ yield {
437
+ type: "response.output_text.done",
438
+ output_index: outputIndex,
439
+ content_index: contentIndex,
440
+ item_id: message.id,
441
+ text: accumulated
442
+ };
443
+ yield {
444
+ type: "response.content_part.done",
445
+ output_index: outputIndex,
446
+ content_index: contentIndex,
447
+ item_id: message.id,
448
+ part: textPart
449
+ };
450
+ yield {
451
+ type: "response.output_item.done",
452
+ output_index: outputIndex,
453
+ item: message
454
+ };
455
+ response = mergeResponseSnapshot(response, {
456
+ status: "completed",
457
+ output: [message],
458
+ error: null
459
+ });
460
+ yield { type: "response.completed", response };
461
+ }
462
+ };
463
+ async function* iterateTextSource(source) {
464
+ if (typeof source === "string") {
465
+ yield source;
466
+ return;
467
+ }
468
+ if (typeof source === "function") {
469
+ yield await source();
470
+ return;
471
+ }
472
+ if (isAsyncIterable(source)) {
473
+ for await (const chunk of source) {
474
+ yield chunk;
475
+ }
476
+ return;
477
+ }
478
+ for (const chunk of source) {
479
+ yield chunk;
480
+ }
481
+ }
482
+ function isAsyncIterable(value) {
483
+ return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
484
+ }
485
+
486
+ export {
487
+ newId,
488
+ newResponseId,
489
+ newMessageItemId,
490
+ newOutputMessageItemId,
491
+ extractPartitionKey,
492
+ isValidId,
493
+ RESPONSE_ID_HEADER,
494
+ SESSION_ID_HEADER,
495
+ normalizeCreateResponse,
496
+ applyCreateResponseDefaults,
497
+ resolveConversationId,
498
+ resolveResponseId,
499
+ validateResponseId,
500
+ resolveAgentReference,
501
+ resolveSessionId,
502
+ deriveSessionId,
503
+ getInputExpanded,
504
+ normalizeResponseInput,
505
+ toOutputItem,
506
+ extractTextFromItems,
507
+ createResponseObject,
508
+ startResponsesSseResponse,
509
+ createSseEncoderState,
510
+ encodeSseEvent,
511
+ encodeKeepAliveComment,
512
+ TextResponse
513
+ };