@tangle-network/agent-runtime 0.17.1 → 0.18.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/README.md +0 -1
- package/dist/agent.d.ts +2 -164
- package/dist/agent.js +0 -108
- package/dist/agent.js.map +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.js +98 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -187,12 +187,9 @@ declare function handleChatTurn(input: RunChatTurnInput): ChatTurnResult;
|
|
|
187
187
|
* opaque id. Substrate executionIds are not a secrecy boundary.
|
|
188
188
|
*
|
|
189
189
|
* Wire integration:
|
|
190
|
-
* -
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* - For cross-process reconnect today, bypass the SDK and POST to the
|
|
194
|
-
* orchestrator's `/agents/run/stream` directly with this id in the
|
|
195
|
-
* `X-Execution-ID` header (see tax-agent's `sessions.ts`).
|
|
190
|
+
* - Sandbox PromptOptions accepts `executionId` and `lastEventId`.
|
|
191
|
+
* Products pass this id to make cross-process reconnect land on the
|
|
192
|
+
* same substrate execution instead of spawning a duplicate run.
|
|
196
193
|
*/
|
|
197
194
|
declare function deriveExecutionId(input: {
|
|
198
195
|
projectId: string;
|
package/dist/index.js
CHANGED
|
@@ -177,6 +177,7 @@ function createOpenAICompatibleBackend(options) {
|
|
|
177
177
|
const requestBody = JSON.stringify({
|
|
178
178
|
model: options.model,
|
|
179
179
|
stream: true,
|
|
180
|
+
stream_options: { include_usage: true },
|
|
180
181
|
messages: input.messages ?? [
|
|
181
182
|
{ role: "user", content: input.message ?? context.task.intent }
|
|
182
183
|
]
|
|
@@ -231,7 +232,7 @@ function createOpenAICompatibleBackend(options) {
|
|
|
231
232
|
status: lastStatus || 0
|
|
232
233
|
});
|
|
233
234
|
}
|
|
234
|
-
yield* streamResponseEvents(response, context);
|
|
235
|
+
yield* streamResponseEvents(response, context, options.model);
|
|
235
236
|
}
|
|
236
237
|
};
|
|
237
238
|
}
|
|
@@ -337,12 +338,14 @@ function mapCommonBackendEvent(event, context) {
|
|
|
337
338
|
}
|
|
338
339
|
return void 0;
|
|
339
340
|
}
|
|
340
|
-
async function* streamResponseEvents(response, context) {
|
|
341
|
+
async function* streamResponseEvents(response, context, requestedModel) {
|
|
341
342
|
const body = response.body;
|
|
342
343
|
if (!body) return;
|
|
343
344
|
const reader = body.getReader();
|
|
344
345
|
const decoder = new TextDecoder();
|
|
345
346
|
let buffer = "";
|
|
347
|
+
const usage = { saw: false };
|
|
348
|
+
const startedAt = Date.now();
|
|
346
349
|
for (; ; ) {
|
|
347
350
|
const { done, value } = await reader.read();
|
|
348
351
|
if (done) break;
|
|
@@ -352,16 +355,32 @@ async function* streamResponseEvents(response, context) {
|
|
|
352
355
|
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
353
356
|
for (const event of drainStreamBuffer(true)) yield event;
|
|
354
357
|
if (buffer.trim()) {
|
|
355
|
-
const event = parseStreamChunk(buffer, context);
|
|
358
|
+
const event = parseStreamChunk(buffer, context, usage);
|
|
356
359
|
if (event) yield event;
|
|
357
360
|
}
|
|
361
|
+
if (usage.saw) {
|
|
362
|
+
yield {
|
|
363
|
+
type: "llm_call",
|
|
364
|
+
task: context.task,
|
|
365
|
+
session: context.session,
|
|
366
|
+
model: usage.model ?? requestedModel,
|
|
367
|
+
tokensIn: usage.tokensIn,
|
|
368
|
+
tokensOut: usage.tokensOut,
|
|
369
|
+
// `costUsd` is intentionally absent — pricing tables live in consumers
|
|
370
|
+
// (agent-eval's `estimateCost`, MetricsCollector). Emitting a wrong
|
|
371
|
+
// number here is worse than emitting none.
|
|
372
|
+
latencyMs: Date.now() - startedAt,
|
|
373
|
+
finishReason: usage.finishReason,
|
|
374
|
+
timestamp: nowIso()
|
|
375
|
+
};
|
|
376
|
+
}
|
|
358
377
|
function* drainStreamBuffer(flush) {
|
|
359
378
|
for (; ; ) {
|
|
360
379
|
const sseBoundary = buffer.indexOf("\n\n");
|
|
361
380
|
if (sseBoundary >= 0) {
|
|
362
381
|
const chunk = buffer.slice(0, sseBoundary);
|
|
363
382
|
buffer = buffer.slice(sseBoundary + 2);
|
|
364
|
-
const event = parseStreamChunk(chunk, context);
|
|
383
|
+
const event = parseStreamChunk(chunk, context, usage);
|
|
365
384
|
if (event) yield event;
|
|
366
385
|
continue;
|
|
367
386
|
}
|
|
@@ -369,14 +388,14 @@ async function* streamResponseEvents(response, context) {
|
|
|
369
388
|
if (newline >= 0 && !buffer.slice(0, newline).startsWith("data:")) {
|
|
370
389
|
const line = buffer.slice(0, newline);
|
|
371
390
|
buffer = buffer.slice(newline + 1);
|
|
372
|
-
const event = parseStreamChunk(line, context);
|
|
391
|
+
const event = parseStreamChunk(line, context, usage);
|
|
373
392
|
if (event) yield event;
|
|
374
393
|
continue;
|
|
375
394
|
}
|
|
376
395
|
if (flush && buffer.trim() && !buffer.trimStart().startsWith("data:")) {
|
|
377
396
|
const line = buffer;
|
|
378
397
|
buffer = "";
|
|
379
|
-
const event = parseStreamChunk(line, context);
|
|
398
|
+
const event = parseStreamChunk(line, context, usage);
|
|
380
399
|
if (event) yield event;
|
|
381
400
|
continue;
|
|
382
401
|
}
|
|
@@ -384,13 +403,14 @@ async function* streamResponseEvents(response, context) {
|
|
|
384
403
|
}
|
|
385
404
|
}
|
|
386
405
|
}
|
|
387
|
-
function parseStreamChunk(chunk, context) {
|
|
406
|
+
function parseStreamChunk(chunk, context, usage) {
|
|
388
407
|
const lines = chunk.split(/\r?\n/);
|
|
389
408
|
const dataLines = lines.filter((line) => line.startsWith("data:"));
|
|
390
409
|
const data = dataLines.length > 0 ? dataLines.map((line) => line.slice(5).trimStart()).join("\n") : chunk.trim();
|
|
391
410
|
if (!data || data === "[DONE]") return void 0;
|
|
392
411
|
try {
|
|
393
412
|
const parsed = JSON.parse(data);
|
|
413
|
+
captureStreamUsage(parsed, usage);
|
|
394
414
|
const choices = parsed.choices;
|
|
395
415
|
const choice = Array.isArray(choices) ? choices[0] : void 0;
|
|
396
416
|
const delta = choice?.delta;
|
|
@@ -405,6 +425,19 @@ function parseStreamChunk(chunk, context) {
|
|
|
405
425
|
timestamp: nowIso()
|
|
406
426
|
};
|
|
407
427
|
}
|
|
428
|
+
if (stringValue(parsed.type) === "content_block_delta") {
|
|
429
|
+
const d = parsed.delta;
|
|
430
|
+
const text2 = stringValue(d?.text);
|
|
431
|
+
if (text2) {
|
|
432
|
+
return {
|
|
433
|
+
type: "text_delta",
|
|
434
|
+
task: context.task,
|
|
435
|
+
session: context.session,
|
|
436
|
+
text: text2,
|
|
437
|
+
timestamp: nowIso()
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
408
441
|
return mapCommonBackendEvent(parsed, context);
|
|
409
442
|
} catch {
|
|
410
443
|
return {
|
|
@@ -416,6 +449,63 @@ function parseStreamChunk(chunk, context) {
|
|
|
416
449
|
};
|
|
417
450
|
}
|
|
418
451
|
}
|
|
452
|
+
function captureStreamUsage(parsed, usage) {
|
|
453
|
+
const model = stringValue(parsed.model);
|
|
454
|
+
if (model && !usage.model) usage.model = model;
|
|
455
|
+
const openAiUsage = parsed.usage;
|
|
456
|
+
if (openAiUsage && typeof openAiUsage === "object") {
|
|
457
|
+
const promptTokens = numberValue(openAiUsage.prompt_tokens);
|
|
458
|
+
const completionTokens = numberValue(openAiUsage.completion_tokens);
|
|
459
|
+
const inputTokens = numberValue(openAiUsage.input_tokens);
|
|
460
|
+
const outputTokens = numberValue(openAiUsage.output_tokens);
|
|
461
|
+
if (promptTokens !== void 0) {
|
|
462
|
+
usage.tokensIn = promptTokens;
|
|
463
|
+
usage.saw = true;
|
|
464
|
+
} else if (inputTokens !== void 0) {
|
|
465
|
+
usage.tokensIn = (usage.tokensIn ?? 0) + inputTokens;
|
|
466
|
+
usage.saw = true;
|
|
467
|
+
}
|
|
468
|
+
if (completionTokens !== void 0) {
|
|
469
|
+
usage.tokensOut = completionTokens;
|
|
470
|
+
usage.saw = true;
|
|
471
|
+
} else if (outputTokens !== void 0) {
|
|
472
|
+
usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
|
|
473
|
+
usage.saw = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const type = stringValue(parsed.type);
|
|
477
|
+
if (type === "message_start") {
|
|
478
|
+
const message = parsed.message;
|
|
479
|
+
const messageModel = stringValue(message?.model);
|
|
480
|
+
if (messageModel && !usage.model) usage.model = messageModel;
|
|
481
|
+
const messageUsage = message?.usage;
|
|
482
|
+
const inputTokens = numberValue(messageUsage?.input_tokens);
|
|
483
|
+
if (inputTokens !== void 0) {
|
|
484
|
+
usage.tokensIn = inputTokens;
|
|
485
|
+
usage.saw = true;
|
|
486
|
+
}
|
|
487
|
+
const outputTokens = numberValue(messageUsage?.output_tokens);
|
|
488
|
+
if (outputTokens !== void 0) {
|
|
489
|
+
usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
|
|
490
|
+
usage.saw = true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (type === "message_delta") {
|
|
494
|
+
const delta = parsed.delta;
|
|
495
|
+
const stopReason = stringValue(delta?.stop_reason);
|
|
496
|
+
if (stopReason) usage.finishReason = stopReason;
|
|
497
|
+
}
|
|
498
|
+
const choices = parsed.choices;
|
|
499
|
+
if (Array.isArray(choices)) {
|
|
500
|
+
const finishReason = stringValue(
|
|
501
|
+
choices[0]?.finish_reason
|
|
502
|
+
);
|
|
503
|
+
if (finishReason) usage.finishReason = finishReason;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function numberValue(value) {
|
|
507
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
508
|
+
}
|
|
419
509
|
function stringValue(value) {
|
|
420
510
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
421
511
|
}
|
|
@@ -462,13 +552,7 @@ function handleChatTurn(input) {
|
|
|
462
552
|
}
|
|
463
553
|
const rawFinal = producer.finalText();
|
|
464
554
|
const finalText = hooks.transformFinalText ? await hooks.transformFinalText(rawFinal) : rawFinal;
|
|
465
|
-
|
|
466
|
-
await hooks.persistAssistantMessage({ identity, finalText });
|
|
467
|
-
} catch (err) {
|
|
468
|
-
log("[chat-engine] persistAssistantMessage threw", {
|
|
469
|
-
error: err instanceof Error ? err.message : String(err)
|
|
470
|
-
});
|
|
471
|
-
}
|
|
555
|
+
await hooks.persistAssistantMessage({ identity, finalText });
|
|
472
556
|
if (hooks.onTurnComplete) {
|
|
473
557
|
try {
|
|
474
558
|
await hooks.onTurnComplete({ identity, finalText });
|