@tangle-network/agent-runtime 0.126.0 → 0.131.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 +70 -20
- package/dist/{activation-DhWJ3p8N.js → activation-BCzMOTaV.js} +3 -3
- package/dist/{activation-DhWJ3p8N.js.map → activation-BCzMOTaV.js.map} +1 -1
- package/dist/agent.d.ts +2 -3
- package/dist/agent.js +4 -5
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-DvSciOfB.js → analyst-loop-BE8cDs5Q.js} +2 -2
- package/dist/{analyst-loop-DvSciOfB.js.map → analyst-loop-BE8cDs5Q.js.map} +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/analyst-loop.js +1 -1
- package/dist/authoring-CvHwo1oW.js +163 -0
- package/dist/authoring-CvHwo1oW.js.map +1 -0
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{candidate-execution-BFpq-Xi6.js → candidate-execution-BNxKr-Bu.js} +5 -5
- package/dist/{candidate-execution-BFpq-Xi6.js.map → candidate-execution-BNxKr-Bu.js.map} +1 -1
- package/dist/{conversation-BpLQZGPH.js → conversation-DNtxaJ1Z.js} +113 -31
- package/dist/conversation-DNtxaJ1Z.js.map +1 -0
- package/dist/conversation.d.ts +2 -2
- package/dist/conversation.js +2 -2
- package/dist/environment-provider-CxvSd1W6.d.ts +86 -0
- package/dist/{environment-provider-DqFS6FSZ.js → environment-provider-Dyg8DtLK.js} +8 -4
- package/dist/environment-provider-Dyg8DtLK.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/graph-BJTxGOFB.js +471 -0
- package/dist/graph-BJTxGOFB.js.map +1 -0
- package/dist/{improvement-cycle-IJgCbKWQ.js → improvement-cycle-Csp38cWg.js} +133 -224
- package/dist/improvement-cycle-Csp38cWg.js.map +1 -0
- package/dist/{index-EdjCQBV9.d.ts → index-CoO7atyo.d.ts} +640 -1184
- package/dist/{index-DIV33AF5.d.ts → index-DwGtu9nc.d.ts} +7 -9
- package/dist/{index-Efjb3nrQ.d.ts → index-qYHpsmG2.d.ts} +36 -18
- package/dist/index.d.ts +353 -11
- package/dist/index.js +111 -354
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +6 -6
- package/dist/intelligence.js +9 -8
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +7 -5
- package/dist/kernel.js +13 -9
- package/dist/{knowledge-EnuEqm_Y.js → knowledge-ce0_uKCl.js} +19 -17
- package/dist/knowledge-ce0_uKCl.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-Bo29_fiD.d.ts → loop-runner-bin-BwgZ8m_7.d.ts} +6 -3
- package/dist/{loop-runner-bin-qwT_4F5I.js → loop-runner-bin-DSbuDDqM.js} +5 -27
- package/dist/loop-runner-bin-DSbuDDqM.js.map +1 -0
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/materialization-COJ1UYQ-.js +272 -0
- package/dist/materialization-COJ1UYQ-.js.map +1 -0
- package/dist/mcp/bin.js +39 -47
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +24 -30
- package/dist/mcp/index.js +66 -83
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.js +1 -1
- package/dist/{memory-server-DL6cE2Ag.js → memory-server-5HEJH672.js} +2 -2
- package/dist/{memory-server-DL6cE2Ag.js.map → memory-server-5HEJH672.js.map} +1 -1
- package/dist/model-policy-CqziaqS1.js +232 -0
- package/dist/model-policy-CqziaqS1.js.map +1 -0
- package/dist/{openai-tools-B68JaOCx.d.ts → openai-tools-D3oMyVGl.d.ts} +2 -2
- package/dist/{openai-tools-Bp1KSkP6.js → openai-tools-ru75mLjq.js} +2 -2
- package/dist/openai-tools-ru75mLjq.js.map +1 -0
- package/dist/{prepare--8EvLqCr.js → prepare-DYWjVcPx.js} +169 -153
- package/dist/prepare-DYWjVcPx.js.map +1 -0
- package/dist/primeintellect/index.d.ts +7 -6
- package/dist/primeintellect/index.js +9 -11
- package/dist/primeintellect/index.js.map +1 -1
- package/dist/profiles.d.ts +21 -174
- package/dist/profiles.js +67 -276
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-CXVfOUu_.js → protected-model-port-48ALLGxT.js} +2 -2
- package/dist/{protected-model-port-CXVfOUu_.js.map → protected-model-port-48ALLGxT.js.map} +1 -1
- package/dist/{redact-PQzmE1Jn.d.ts → redact-9_Gf-8m3.d.ts} +58 -69
- package/dist/{researcher-CoVqNhfI.js → researcher-Skz5-Uc8.js} +50 -26
- package/dist/researcher-Skz5-Uc8.js.map +1 -0
- package/dist/{run-layout-C2FGmZ3v.js → run-layout-CeJsEAom.js} +2 -2
- package/dist/{run-layout-C2FGmZ3v.js.map → run-layout-CeJsEAom.js.map} +1 -1
- package/dist/runtime-D-QfLbSd.d.ts +893 -0
- package/dist/{runtime-BzXz7OjS.js → runtime-hiAABiTk.js} +329 -854
- package/dist/runtime-hiAABiTk.js.map +1 -0
- package/dist/{sandbox-events-Yhd1GYWl.js → sandbox-events-CRDwc5WN.js} +42 -5
- package/dist/sandbox-events-CRDwc5WN.js.map +1 -0
- package/dist/snapshot-CXiiuHhL.js +21 -0
- package/dist/snapshot-CXiiuHhL.js.map +1 -0
- package/dist/{spawn-journal-DsZKDqeh.js → spawn-journal-saHQzqYi.js} +15 -21
- package/dist/spawn-journal-saHQzqYi.js.map +1 -0
- package/dist/stream-agent-turn-C852AgMT.d.ts +128 -0
- package/dist/stream-agent-turn-rYgaOLO0.js +910 -0
- package/dist/stream-agent-turn-rYgaOLO0.js.map +1 -0
- package/dist/{structural-rollout-zY0oqQzO.js → structural-rollout-3uxVGcg2.js} +459 -235
- package/dist/structural-rollout-3uxVGcg2.js.map +1 -0
- package/dist/{supervise-Ds8FtyI9.js → supervise-iPN27pO0.js} +932 -4771
- package/dist/supervise-iPN27pO0.js.map +1 -0
- package/dist/{supervisor-DpjO0Gmy.js → supervisor-CV6Jh28D.js} +7439 -2897
- package/dist/supervisor-CV6Jh28D.js.map +1 -0
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +271 -221
- package/dist/testing.js.map +1 -1
- package/dist/{top-app-5unxqovu.js → top-app-G4M18b6i.js} +3 -3
- package/dist/{top-app-5unxqovu.js.map → top-app-G4M18b6i.js.map} +1 -1
- package/dist/tui/bin.js +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{environment-provider-PM9PeW_J.d.ts → types-C6Q-J0Dt.d.ts} +57 -114
- package/dist/{types-DnNGJ5Gz.d.ts → types-ebIY0dMG.d.ts} +556 -30
- package/dist/{util-MVgdwuIS.js → util-Bw6srryQ.js} +3 -2
- package/dist/{util-MVgdwuIS.js.map → util-Bw6srryQ.js.map} +1 -1
- package/dist/{workspace-archive-BQxvkypI.js → workspace-archive-aOfJ47ms.js} +3 -3
- package/dist/{workspace-archive-BQxvkypI.js.map → workspace-archive-aOfJ47ms.js.map} +1 -1
- package/package.json +12 -15
- package/skills/agent-graphs/IMPROVE.md +58 -0
- package/skills/agent-graphs/SKILL.md +139 -0
- package/skills/agent-graphs/cases/artifact-mission-release-notes.json +10 -0
- package/skills/agent-graphs/cases/audited-single-writer.json +9 -0
- package/skills/agent-graphs/cases/cap-as-stop-mistake.json +8 -0
- package/skills/agent-graphs/cases/mission-in-deliverable.json +8 -0
- package/skills/agent-graphs/cases/review-pipeline.json +13 -0
- package/skills/agent-graphs/cases/runtime-discovered-fanout.json +8 -0
- package/skills/agent-graphs/cases/single-agent-suffices.json +7 -0
- package/skills/agent-graphs/cases/steer-heavy-drafting.json +9 -0
- package/skills/agent-graphs/cases/unmeasured-harness.json +7 -0
- package/skills/agent-graphs/generations/gen1-baseline.json +248 -0
- package/skills/agent-graphs/generations/gen2.json +375 -0
- package/skills/agent-graphs/generations/gen3.json +702 -0
- package/skills/build-with-agent-runtime/SKILL.md +1 -0
- package/dist/backends-CiOCyRHb.js +0 -743
- package/dist/backends-CiOCyRHb.js.map +0 -1
- package/dist/conversation-BpLQZGPH.js.map +0 -1
- package/dist/environment-provider-DqFS6FSZ.js.map +0 -1
- package/dist/improvement-cycle-IJgCbKWQ.js.map +0 -1
- package/dist/index-D_M4d1_B.d.ts +0 -545
- package/dist/knowledge-EnuEqm_Y.js.map +0 -1
- package/dist/local-harness-BIajef4A.d.ts +0 -465
- package/dist/loop-runner-bin-qwT_4F5I.js.map +0 -1
- package/dist/model-resolution-Btd9iIKV.js +0 -98
- package/dist/model-resolution-Btd9iIKV.js.map +0 -1
- package/dist/openai-tools-Bp1KSkP6.js.map +0 -1
- package/dist/prepare--8EvLqCr.js.map +0 -1
- package/dist/researcher-CoVqNhfI.js.map +0 -1
- package/dist/runtime-BzXz7OjS.js.map +0 -1
- package/dist/sandbox-events-Yhd1GYWl.js.map +0 -1
- package/dist/spawn-journal-DsZKDqeh.js.map +0 -1
- package/dist/structural-rollout-zY0oqQzO.js.map +0 -1
- package/dist/supervise-Ds8FtyI9.js.map +0 -1
- package/dist/supervisor-DpjO0Gmy.js.map +0 -1
- package/dist/types-C9j4qg6l.d.ts +0 -500
|
@@ -1,743 +0,0 @@
|
|
|
1
|
-
import { l as SessionMismatchError, r as BackendTransportError } from "./errors-DEAvWQPy.js";
|
|
2
|
-
//#region src/sessions.ts
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* Session helpers + an in-memory `RuntimeSessionStore` implementation suitable
|
|
6
|
-
* for tests, scratch processes, and per-request scratch storage in serverless
|
|
7
|
-
* runtimes. Durable stores (D1, postgres, Durable Objects) implement the same
|
|
8
|
-
* interface from `./types`.
|
|
9
|
-
*
|
|
10
|
-
* @stable
|
|
11
|
-
*/
|
|
12
|
-
/** @internal */
|
|
13
|
-
function newRuntimeSession(backend, requestedId, metadata) {
|
|
14
|
-
const now = nowIso();
|
|
15
|
-
return {
|
|
16
|
-
id: requestedId || crypto.randomUUID(),
|
|
17
|
-
backend,
|
|
18
|
-
status: "active",
|
|
19
|
-
createdAt: now,
|
|
20
|
-
updatedAt: now,
|
|
21
|
-
metadata
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
/** @internal */
|
|
25
|
-
function touchSession(session) {
|
|
26
|
-
return {
|
|
27
|
-
...session,
|
|
28
|
-
updatedAt: nowIso()
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
/** @internal */
|
|
32
|
-
function nowIso() {
|
|
33
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
34
|
-
}
|
|
35
|
-
/** Start or resume one backend session and persist its current identity. @internal */
|
|
36
|
-
async function startOrResumeRuntimeSession(options) {
|
|
37
|
-
const existing = options.resume && options.sessionId ? await options.store?.get(options.sessionId) : void 0;
|
|
38
|
-
if (!existing) {
|
|
39
|
-
const input = resolveSessionInput(options.input);
|
|
40
|
-
const session = options.backend.start ? await options.backend.start(input, {
|
|
41
|
-
...options.context,
|
|
42
|
-
requestedSessionId: options.sessionId
|
|
43
|
-
}) : newRuntimeSession(options.backend.kind, options.sessionId);
|
|
44
|
-
assertSessionBackend(session, options.backend.kind);
|
|
45
|
-
options.validateSession?.(session);
|
|
46
|
-
await options.store?.put(session);
|
|
47
|
-
return {
|
|
48
|
-
session,
|
|
49
|
-
input,
|
|
50
|
-
resumed: false
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
assertSessionBackend(existing, options.backend.kind);
|
|
54
|
-
const input = resolveSessionInput(options.backend.resume !== void 0 ? options.continuationInput ?? options.input : options.input);
|
|
55
|
-
const session = options.backend.resume ? await options.backend.resume(existing, input, options.context) : touchSession({
|
|
56
|
-
...existing,
|
|
57
|
-
status: "active"
|
|
58
|
-
});
|
|
59
|
-
assertSessionBackend(session, options.backend.kind);
|
|
60
|
-
options.validateSession?.(session);
|
|
61
|
-
await options.store?.put(session);
|
|
62
|
-
return {
|
|
63
|
-
session,
|
|
64
|
-
input,
|
|
65
|
-
resumed: true
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function resolveSessionInput(input) {
|
|
69
|
-
return typeof input === "function" ? input() : input;
|
|
70
|
-
}
|
|
71
|
-
function assertSessionBackend(session, backend) {
|
|
72
|
-
if (session.backend !== backend) throw new SessionMismatchError(session.backend, backend);
|
|
73
|
-
}
|
|
74
|
-
/** In-memory `RuntimeSessionStore` for single-process use and tests. @stable */
|
|
75
|
-
var InMemoryRuntimeSessionStore = class {
|
|
76
|
-
sessions = /* @__PURE__ */ new Map();
|
|
77
|
-
events = /* @__PURE__ */ new Map();
|
|
78
|
-
get(sessionId) {
|
|
79
|
-
return this.sessions.get(sessionId);
|
|
80
|
-
}
|
|
81
|
-
put(session) {
|
|
82
|
-
this.sessions.set(session.id, session);
|
|
83
|
-
}
|
|
84
|
-
appendEvent(sessionId, event) {
|
|
85
|
-
const existing = this.events.get(sessionId) ?? [];
|
|
86
|
-
existing.push(event);
|
|
87
|
-
this.events.set(sessionId, existing);
|
|
88
|
-
}
|
|
89
|
-
listEvents(sessionId) {
|
|
90
|
-
return [...this.events.get(sessionId) ?? []];
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/backends.ts
|
|
95
|
-
/**
|
|
96
|
-
*
|
|
97
|
-
* Backend factories for `runAgentTaskStream`. Three shapes ship in core:
|
|
98
|
-
*
|
|
99
|
-
* - `createIterableBackend` — wrap any custom async iterable into a backend
|
|
100
|
-
* - `createSandboxPromptBackend` — sandbox / sidecar `streamPrompt` clients
|
|
101
|
-
* - `createOpenAICompatibleBackend` — OpenAI-style chat completions endpoints
|
|
102
|
-
*
|
|
103
|
-
* Adapters stay thin: domain repos own auth, model selection, and the concrete
|
|
104
|
-
* tool surface. The factories handle session creation, stream normalization,
|
|
105
|
-
* and graceful end-of-stream signalling.
|
|
106
|
-
*
|
|
107
|
-
* @stable
|
|
108
|
-
*/
|
|
109
|
-
/** Wrap any custom async-iterable stream into a typed `AgentExecutionBackend`. @stable */
|
|
110
|
-
function createIterableBackend(options) {
|
|
111
|
-
return options;
|
|
112
|
-
}
|
|
113
|
-
/** Build an `AgentExecutionBackend` backed by a sandbox/sidecar `streamPrompt` call. @stable */
|
|
114
|
-
function createSandboxPromptBackend(options) {
|
|
115
|
-
const kind = options.kind ?? "sandbox";
|
|
116
|
-
return {
|
|
117
|
-
kind,
|
|
118
|
-
async start(input, context) {
|
|
119
|
-
const box = await options.getBox(input, context);
|
|
120
|
-
return newRuntimeSession(kind, options.getSessionId?.(box, input) ?? context.requestedSessionId, { resumable: true });
|
|
121
|
-
},
|
|
122
|
-
resume(session) {
|
|
123
|
-
return touchSession({
|
|
124
|
-
...session,
|
|
125
|
-
status: "active"
|
|
126
|
-
});
|
|
127
|
-
},
|
|
128
|
-
async *stream(input, context) {
|
|
129
|
-
const box = await options.getBox(input, context);
|
|
130
|
-
const message = input.message ?? input.messages?.at(-1)?.content ?? context.task.intent;
|
|
131
|
-
for await (const event of options.streamPrompt(box, message, context)) {
|
|
132
|
-
const mapped = options.mapEvent?.(event, context) ?? mapCommonBackendEvent(event, context);
|
|
133
|
-
if (mapped) yield mapped;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
const DEFAULT_RETRY_STATUSES = [
|
|
139
|
-
408,
|
|
140
|
-
425,
|
|
141
|
-
429,
|
|
142
|
-
500,
|
|
143
|
-
502,
|
|
144
|
-
503,
|
|
145
|
-
504
|
|
146
|
-
];
|
|
147
|
-
function pickRetryDelayMs(attempt, policy) {
|
|
148
|
-
const exp = policy.initialBackoffMs * 2 ** (attempt - 1);
|
|
149
|
-
const capped = Math.min(exp, policy.maxBackoffMs);
|
|
150
|
-
const jitter = capped * policy.jitter * (Math.random() * 2 - 1);
|
|
151
|
-
return Math.max(0, Math.round(capped + jitter));
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Derive a per-attempt AbortSignal that fires when EITHER the caller's
|
|
155
|
-
* signal aborts OR `timeoutMs` elapses. `dispose()` clears the timer so a
|
|
156
|
-
* completed attempt doesn't leak a pending timeout. `timeoutMs <= 0`
|
|
157
|
-
* disables the deadline (caller signal still propagates).
|
|
158
|
-
*/
|
|
159
|
-
function withTimeout(callerSignal, timeoutMs) {
|
|
160
|
-
if (timeoutMs <= 0) return {
|
|
161
|
-
signal: callerSignal ?? new AbortController().signal,
|
|
162
|
-
dispose: () => void 0
|
|
163
|
-
};
|
|
164
|
-
const controller = new AbortController();
|
|
165
|
-
const timer = setTimeout(() => controller.abort(/* @__PURE__ */ new Error(`request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
166
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
167
|
-
const onCallerAbort = () => controller.abort(callerSignal?.reason ?? /* @__PURE__ */ new Error("aborted"));
|
|
168
|
-
if (callerSignal) if (callerSignal.aborted) onCallerAbort();
|
|
169
|
-
else callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
170
|
-
return {
|
|
171
|
-
signal: controller.signal,
|
|
172
|
-
dispose: () => {
|
|
173
|
-
clearTimeout(timer);
|
|
174
|
-
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
function sleep(ms, signal) {
|
|
179
|
-
return new Promise((resolve, reject) => {
|
|
180
|
-
if (signal?.aborted) {
|
|
181
|
-
reject(signal.reason ?? /* @__PURE__ */ new Error("aborted"));
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
const t = setTimeout(() => {
|
|
185
|
-
signal?.removeEventListener("abort", onAbort);
|
|
186
|
-
resolve();
|
|
187
|
-
}, ms);
|
|
188
|
-
const onAbort = () => {
|
|
189
|
-
clearTimeout(t);
|
|
190
|
-
reject(signal?.reason ?? /* @__PURE__ */ new Error("aborted"));
|
|
191
|
-
};
|
|
192
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
*
|
|
197
|
-
* OpenAI-compat streaming backend. Routes `runAgentTaskStream` through any
|
|
198
|
-
* `POST /chat/completions` endpoint that speaks OpenAI's SSE protocol —
|
|
199
|
-
* Tangle Router, OpenAI direct, OpenRouter, Groq, DeepSeek, Together. The
|
|
200
|
-
* router also fronts Anthropic models in Anthropic-native SSE shape; this
|
|
201
|
-
* backend handles both.
|
|
202
|
-
*
|
|
203
|
-
* ### Tool calls
|
|
204
|
-
*
|
|
205
|
-
* Pass `tools` (and optionally `toolChoice`) to forward an OpenAI Chat
|
|
206
|
-
* Completions `tools[]` array on every request. Streamed `tool_call` chunks
|
|
207
|
-
* are buffered until the model finalizes them (either `finish_reason:
|
|
208
|
-
* 'tool_calls'` for OpenAI shape or a `content_block_stop` for Anthropic
|
|
209
|
-
* `tool_use` blocks proxied through the router), then emitted as a single
|
|
210
|
-
* `tool_call` RuntimeStreamEvent with the assembled `args`.
|
|
211
|
-
*
|
|
212
|
-
* The backend does NOT execute tools — it surfaces calls for the caller's
|
|
213
|
-
* own dispatcher (typically the product's MCP / sandbox runtime) to fulfill
|
|
214
|
-
* and feed back as a subsequent `messages` turn. This keeps the transport
|
|
215
|
-
* thin and lets the agent host own tool dispatch policy.
|
|
216
|
-
*
|
|
217
|
-
* ### Fail-loud errors
|
|
218
|
-
*
|
|
219
|
-
* Non-success HTTP responses (4xx/5xx) and exhausted retry budgets throw
|
|
220
|
-
* `BackendTransportError` from inside the `stream()` generator. The runtime
|
|
221
|
-
* catches the throw, yields a `backend_error` with a typed `error` field
|
|
222
|
-
* (`kind`, `status`, truncated `body`) and a terminal `final` event with
|
|
223
|
-
* `status: 'failed'` carrying the same detail. Consumers MUST map
|
|
224
|
-
* `final.error` onto their `RunRecord.error` — silently treating an empty
|
|
225
|
-
* `finalText` as "agent produced nothing" hides credit exhaustion, auth
|
|
226
|
-
* failure, and upstream outages.
|
|
227
|
-
*
|
|
228
|
-
* @stable
|
|
229
|
-
*/
|
|
230
|
-
function createOpenAICompatibleBackend(options) {
|
|
231
|
-
const fetcher = options.fetchImpl ?? fetch;
|
|
232
|
-
const kind = options.kind ?? "tcloud";
|
|
233
|
-
const retryPolicy = {
|
|
234
|
-
maxAttempts: options.retry?.maxAttempts ?? 5,
|
|
235
|
-
initialBackoffMs: options.retry?.initialBackoffMs ?? 1e3,
|
|
236
|
-
maxBackoffMs: options.retry?.maxBackoffMs ?? 3e4,
|
|
237
|
-
jitter: options.retry?.jitter ?? .25,
|
|
238
|
-
retryStatuses: options.retry?.retryStatuses ?? DEFAULT_RETRY_STATUSES,
|
|
239
|
-
requestTimeoutMs: options.retry?.requestTimeoutMs ?? 12e4
|
|
240
|
-
};
|
|
241
|
-
return {
|
|
242
|
-
kind,
|
|
243
|
-
start(_input, context) {
|
|
244
|
-
return newRuntimeSession(kind, context.requestedSessionId);
|
|
245
|
-
},
|
|
246
|
-
async *stream(input, context) {
|
|
247
|
-
const url = `${options.baseUrl.replace(/\/$/, "")}/chat/completions`;
|
|
248
|
-
const bodyPayload = {
|
|
249
|
-
model: options.model,
|
|
250
|
-
stream: true,
|
|
251
|
-
stream_options: { include_usage: true },
|
|
252
|
-
messages: input.messages ?? [{
|
|
253
|
-
role: "user",
|
|
254
|
-
content: input.message ?? context.task.intent
|
|
255
|
-
}]
|
|
256
|
-
};
|
|
257
|
-
if (options.tools && options.tools.length > 0) {
|
|
258
|
-
bodyPayload.tools = options.tools;
|
|
259
|
-
if (options.toolChoice !== void 0) bodyPayload.tool_choice = options.toolChoice;
|
|
260
|
-
}
|
|
261
|
-
if (options.responseFormat !== void 0) bodyPayload.response_format = options.responseFormat;
|
|
262
|
-
if (options.temperature !== void 0) bodyPayload.temperature = options.temperature;
|
|
263
|
-
if (options.maxTokens !== void 0) bodyPayload.max_tokens = options.maxTokens;
|
|
264
|
-
const requestBody = JSON.stringify(bodyPayload);
|
|
265
|
-
let response;
|
|
266
|
-
let lastStatus = 0;
|
|
267
|
-
let lastThrown;
|
|
268
|
-
for (let attempt = 1; attempt <= retryPolicy.maxAttempts; attempt++) {
|
|
269
|
-
lastThrown = void 0;
|
|
270
|
-
const attemptSignal = withTimeout(context.signal, retryPolicy.requestTimeoutMs);
|
|
271
|
-
try {
|
|
272
|
-
response = await fetcher(url, {
|
|
273
|
-
method: "POST",
|
|
274
|
-
headers: {
|
|
275
|
-
Authorization: `Bearer ${options.apiKey}`,
|
|
276
|
-
"Content-Type": "application/json",
|
|
277
|
-
...context.propagatedHeaders ?? {}
|
|
278
|
-
},
|
|
279
|
-
body: requestBody,
|
|
280
|
-
signal: attemptSignal.signal
|
|
281
|
-
});
|
|
282
|
-
} catch (err) {
|
|
283
|
-
attemptSignal.dispose();
|
|
284
|
-
if (context.signal?.aborted) throw err;
|
|
285
|
-
lastThrown = err;
|
|
286
|
-
response = void 0;
|
|
287
|
-
if (attempt === retryPolicy.maxAttempts) break;
|
|
288
|
-
await sleep(pickRetryDelayMs(attempt, retryPolicy), context.signal);
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
attemptSignal.dispose();
|
|
292
|
-
if (response.ok) break;
|
|
293
|
-
lastStatus = response.status;
|
|
294
|
-
if (!retryPolicy.retryStatuses.includes(response.status)) break;
|
|
295
|
-
if (attempt === retryPolicy.maxAttempts) break;
|
|
296
|
-
try {
|
|
297
|
-
await response.body?.cancel();
|
|
298
|
-
} catch {}
|
|
299
|
-
await sleep(pickRetryDelayMs(attempt, retryPolicy), context.signal);
|
|
300
|
-
}
|
|
301
|
-
if (!response) {
|
|
302
|
-
const reason = lastThrown instanceof Error ? lastThrown.message : String(lastThrown);
|
|
303
|
-
throw new BackendTransportError(kind, `chat backend unreachable after ${retryPolicy.maxAttempts} attempts: ${reason}`, { status: 0 });
|
|
304
|
-
}
|
|
305
|
-
if (!response.ok) {
|
|
306
|
-
let body;
|
|
307
|
-
try {
|
|
308
|
-
const raw = await response.text();
|
|
309
|
-
body = raw.length > MAX_ERROR_BODY_BYTES ? `${raw.slice(0, MAX_ERROR_BODY_BYTES)}…` : raw;
|
|
310
|
-
} catch {
|
|
311
|
-
body = void 0;
|
|
312
|
-
}
|
|
313
|
-
throw new BackendTransportError(kind, `chat backend returned ${lastStatus || "unknown"}`, {
|
|
314
|
-
status: lastStatus || 0,
|
|
315
|
-
body
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
yield* streamResponseEvents(response, context, options.model);
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Cap the captured error body. 2 KiB is enough to carry a JSON error envelope
|
|
324
|
-
* with a structured `code`/`message` payload (the router returns ~150 bytes
|
|
325
|
-
* for a free-tier denial; OpenAI returns ~300 bytes for invalid auth) without
|
|
326
|
-
* letting an HTML error page balloon persisted events.
|
|
327
|
-
*/
|
|
328
|
-
const MAX_ERROR_BODY_BYTES = 2048;
|
|
329
|
-
/** @internal */
|
|
330
|
-
function normalizeBackendStreamEvent(event, task, session) {
|
|
331
|
-
if ("task" in event && event.task && "session" in event && event.session && "timestamp" in event && event.timestamp) return event;
|
|
332
|
-
return {
|
|
333
|
-
...event,
|
|
334
|
-
task: "task" in event && event.task ? event.task : task,
|
|
335
|
-
session: "session" in event && event.session ? event.session : session,
|
|
336
|
-
timestamp: "timestamp" in event && event.timestamp ? event.timestamp : nowIso()
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function mapCommonBackendEvent(event, context) {
|
|
340
|
-
if (!event || typeof event !== "object") return void 0;
|
|
341
|
-
const record = event;
|
|
342
|
-
const type = String(record.type ?? "");
|
|
343
|
-
const data = record.data && typeof record.data === "object" ? record.data : record;
|
|
344
|
-
if (type === "message.part.updated" || type === "text_delta" || type === "delta") {
|
|
345
|
-
const part = data.part;
|
|
346
|
-
const partText = part !== void 0 && typeof part === "object" && (part.type === "text" || part.type === void 0) ? stringValue(part.text) : void 0;
|
|
347
|
-
const text = stringValue(data.text) ?? stringValue(data.delta) ?? stringValue(record.text) ?? partText;
|
|
348
|
-
return text ? {
|
|
349
|
-
type: "text_delta",
|
|
350
|
-
task: context.task,
|
|
351
|
-
session: context.session,
|
|
352
|
-
text,
|
|
353
|
-
timestamp: nowIso()
|
|
354
|
-
} : void 0;
|
|
355
|
-
}
|
|
356
|
-
if (type === "reasoning_delta") {
|
|
357
|
-
const text = stringValue(data.text) ?? stringValue(record.text);
|
|
358
|
-
return text ? {
|
|
359
|
-
type: "reasoning_delta",
|
|
360
|
-
task: context.task,
|
|
361
|
-
session: context.session,
|
|
362
|
-
text,
|
|
363
|
-
timestamp: nowIso()
|
|
364
|
-
} : void 0;
|
|
365
|
-
}
|
|
366
|
-
if (type === "tool_call") return {
|
|
367
|
-
type: "tool_call",
|
|
368
|
-
task: context.task,
|
|
369
|
-
session: context.session,
|
|
370
|
-
toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
|
|
371
|
-
toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
|
|
372
|
-
args: data.args ?? data.input ?? record.args,
|
|
373
|
-
timestamp: nowIso()
|
|
374
|
-
};
|
|
375
|
-
if (type === "tool_result") return {
|
|
376
|
-
type: "tool_result",
|
|
377
|
-
task: context.task,
|
|
378
|
-
session: context.session,
|
|
379
|
-
toolName: stringValue(data.name) ?? stringValue(record.toolName) ?? "tool",
|
|
380
|
-
toolCallId: stringValue(data.id) ?? stringValue(record.toolCallId),
|
|
381
|
-
result: data.result ?? data.output ?? record.result,
|
|
382
|
-
timestamp: nowIso()
|
|
383
|
-
};
|
|
384
|
-
if (type === "artifact") {
|
|
385
|
-
const artifactId = stringValue(data.artifactId) ?? stringValue(data.id) ?? stringValue(record.artifactId);
|
|
386
|
-
if (!artifactId) return void 0;
|
|
387
|
-
return {
|
|
388
|
-
type: "artifact",
|
|
389
|
-
task: context.task,
|
|
390
|
-
session: context.session,
|
|
391
|
-
artifactId,
|
|
392
|
-
name: stringValue(data.name) ?? stringValue(record.name),
|
|
393
|
-
mimeType: stringValue(data.mimeType) ?? stringValue(record.mimeType),
|
|
394
|
-
uri: stringValue(data.uri) ?? stringValue(record.uri),
|
|
395
|
-
content: stringValue(data.content) ?? stringValue(data.body) ?? stringValue(record.content),
|
|
396
|
-
metadata: data.metadata && typeof data.metadata === "object" ? data.metadata : void 0,
|
|
397
|
-
timestamp: nowIso()
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
if (type === "proposal_created" || type === "proposal" || type === "filing") {
|
|
401
|
-
const proposalId = stringValue(data.proposalId) ?? stringValue(data.id) ?? stringValue(record.proposalId);
|
|
402
|
-
if (!proposalId) return void 0;
|
|
403
|
-
const status = stringValue(data.status) ?? stringValue(record.status);
|
|
404
|
-
return {
|
|
405
|
-
type: "proposal_created",
|
|
406
|
-
task: context.task,
|
|
407
|
-
session: context.session,
|
|
408
|
-
proposalId,
|
|
409
|
-
title: stringValue(data.title) ?? stringValue(record.title) ?? proposalId,
|
|
410
|
-
status: status === "pending" || status === "approved" || status === "rejected" ? status : void 0,
|
|
411
|
-
content: stringValue(data.content) ?? stringValue(data.body) ?? stringValue(record.content),
|
|
412
|
-
timestamp: nowIso()
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
if (type === "result" || type === "final") {
|
|
416
|
-
const text = stringValue(data.finalText) ?? stringValue(data.text) ?? stringValue(record.text);
|
|
417
|
-
return text ? {
|
|
418
|
-
type: "text_delta",
|
|
419
|
-
task: context.task,
|
|
420
|
-
session: context.session,
|
|
421
|
-
text,
|
|
422
|
-
timestamp: nowIso()
|
|
423
|
-
} : void 0;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
async function* streamResponseEvents(response, context, requestedModel) {
|
|
427
|
-
const body = response.body;
|
|
428
|
-
if (!body) return;
|
|
429
|
-
const reader = body.getReader();
|
|
430
|
-
const decoder = new TextDecoder();
|
|
431
|
-
let buffer = "";
|
|
432
|
-
const usage = { saw: false };
|
|
433
|
-
const toolCalls = /* @__PURE__ */ new Map();
|
|
434
|
-
const startedAt = Date.now();
|
|
435
|
-
for (;;) {
|
|
436
|
-
const { done, value } = await reader.read();
|
|
437
|
-
if (done) break;
|
|
438
|
-
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n");
|
|
439
|
-
for (const event of drainStreamBuffer(false)) yield event;
|
|
440
|
-
}
|
|
441
|
-
buffer += decoder.decode().replace(/\r\n/g, "\n");
|
|
442
|
-
for (const event of drainStreamBuffer(true)) yield event;
|
|
443
|
-
if (buffer.trim()) for (const event of parseStreamChunk(buffer, context, usage, toolCalls)) yield event;
|
|
444
|
-
for (const event of flushPendingToolCalls(toolCalls, context)) yield event;
|
|
445
|
-
if (usage.saw) yield {
|
|
446
|
-
type: "llm_call",
|
|
447
|
-
task: context.task,
|
|
448
|
-
session: context.session,
|
|
449
|
-
model: usage.model ?? requestedModel,
|
|
450
|
-
tokensIn: usage.tokensIn,
|
|
451
|
-
tokensOut: usage.tokensOut,
|
|
452
|
-
latencyMs: Date.now() - startedAt,
|
|
453
|
-
finishReason: usage.finishReason,
|
|
454
|
-
timestamp: nowIso()
|
|
455
|
-
};
|
|
456
|
-
function* drainStreamBuffer(flush) {
|
|
457
|
-
for (;;) {
|
|
458
|
-
const sseBoundary = buffer.indexOf("\n\n");
|
|
459
|
-
if (sseBoundary >= 0) {
|
|
460
|
-
const chunk = buffer.slice(0, sseBoundary);
|
|
461
|
-
buffer = buffer.slice(sseBoundary + 2);
|
|
462
|
-
for (const event of parseStreamChunk(chunk, context, usage, toolCalls)) yield event;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
const newline = buffer.indexOf("\n");
|
|
466
|
-
if (newline >= 0 && !buffer.slice(0, newline).startsWith("data:")) {
|
|
467
|
-
const line = buffer.slice(0, newline);
|
|
468
|
-
buffer = buffer.slice(newline + 1);
|
|
469
|
-
for (const event of parseStreamChunk(line, context, usage, toolCalls)) yield event;
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
if (flush && buffer.trim() && !buffer.trimStart().startsWith("data:")) {
|
|
473
|
-
const line = buffer;
|
|
474
|
-
buffer = "";
|
|
475
|
-
for (const event of parseStreamChunk(line, context, usage, toolCalls)) yield event;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
function* parseStreamChunk(chunk, context, usage, toolCalls) {
|
|
483
|
-
const lines = chunk.split(/\r?\n/);
|
|
484
|
-
const dataLines = lines.filter((line) => line.startsWith("data:"));
|
|
485
|
-
if (dataLines.length === 0 && lines.every((line) => {
|
|
486
|
-
const trimmed = line.trim();
|
|
487
|
-
return trimmed.length === 0 || trimmed.startsWith(":");
|
|
488
|
-
})) return;
|
|
489
|
-
const data = dataLines.length > 0 ? dataLines.map((line) => line.slice(5).trimStart()).join("\n") : chunk.trim();
|
|
490
|
-
if (!data || data === "[DONE]") return;
|
|
491
|
-
let parsed;
|
|
492
|
-
try {
|
|
493
|
-
parsed = JSON.parse(data);
|
|
494
|
-
} catch {
|
|
495
|
-
yield {
|
|
496
|
-
type: "text_delta",
|
|
497
|
-
task: context.task,
|
|
498
|
-
session: context.session,
|
|
499
|
-
text: data,
|
|
500
|
-
timestamp: nowIso()
|
|
501
|
-
};
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
captureStreamUsage(parsed, usage);
|
|
505
|
-
const choices = parsed.choices;
|
|
506
|
-
const choice = Array.isArray(choices) ? choices[0] : void 0;
|
|
507
|
-
const delta = choice?.delta;
|
|
508
|
-
const message = choice?.message;
|
|
509
|
-
const deltaToolCalls = delta?.tool_calls;
|
|
510
|
-
if (Array.isArray(deltaToolCalls)) for (const tc of deltaToolCalls) {
|
|
511
|
-
if (!tc || typeof tc !== "object") continue;
|
|
512
|
-
const rec = tc;
|
|
513
|
-
const id = stringValue(rec.id);
|
|
514
|
-
const key = deltaToolCallKey(rec, id, toolCalls);
|
|
515
|
-
const acc = toolCalls.get(key) ?? {
|
|
516
|
-
argsRaw: "",
|
|
517
|
-
source: "openai",
|
|
518
|
-
finalized: false
|
|
519
|
-
};
|
|
520
|
-
if (id) acc.id = id;
|
|
521
|
-
const fn = rec.function;
|
|
522
|
-
const name = stringValue(fn?.name);
|
|
523
|
-
if (name) acc.name = name;
|
|
524
|
-
const args = stringValue(fn?.arguments);
|
|
525
|
-
if (args) acc.argsRaw += args;
|
|
526
|
-
toolCalls.set(key, acc);
|
|
527
|
-
}
|
|
528
|
-
const messageToolCalls = message?.tool_calls;
|
|
529
|
-
if (Array.isArray(messageToolCalls)) for (const tc of messageToolCalls) {
|
|
530
|
-
if (!tc || typeof tc !== "object") continue;
|
|
531
|
-
const rec = tc;
|
|
532
|
-
const fn = rec.function;
|
|
533
|
-
const key = `openai:${numberValue(rec.index) ?? messageToolCalls.indexOf(tc)}`;
|
|
534
|
-
const acc = toolCalls.get(key) ?? {
|
|
535
|
-
argsRaw: "",
|
|
536
|
-
source: "openai",
|
|
537
|
-
finalized: false
|
|
538
|
-
};
|
|
539
|
-
const id = stringValue(rec.id);
|
|
540
|
-
if (id) acc.id = id;
|
|
541
|
-
const name = stringValue(fn?.name);
|
|
542
|
-
if (name) acc.name = name;
|
|
543
|
-
const args = stringValue(fn?.arguments);
|
|
544
|
-
if (args) acc.argsRaw += args;
|
|
545
|
-
acc.finalized = true;
|
|
546
|
-
toolCalls.set(key, acc);
|
|
547
|
-
}
|
|
548
|
-
if (stringValue(choice?.finish_reason) === "tool_calls") for (const [key, acc] of toolCalls) {
|
|
549
|
-
if (acc.source === "openai" && !acc.finalized) acc.finalized = true;
|
|
550
|
-
toolCalls.set(key, acc);
|
|
551
|
-
}
|
|
552
|
-
const eventType = stringValue(parsed.type);
|
|
553
|
-
if (eventType === "content_block_start") {
|
|
554
|
-
const block = parsed.content_block;
|
|
555
|
-
if (block && stringValue(block.type) === "tool_use") {
|
|
556
|
-
const key = `anthropic:${numberValue(parsed.index) ?? 0}`;
|
|
557
|
-
toolCalls.set(key, {
|
|
558
|
-
id: stringValue(block.id),
|
|
559
|
-
name: stringValue(block.name),
|
|
560
|
-
argsRaw: "",
|
|
561
|
-
source: "anthropic",
|
|
562
|
-
finalized: false
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (eventType === "content_block_delta") {
|
|
567
|
-
const d = parsed.delta;
|
|
568
|
-
if (stringValue(d?.type) === "input_json_delta") {
|
|
569
|
-
const key = `anthropic:${numberValue(parsed.index) ?? 0}`;
|
|
570
|
-
const acc = toolCalls.get(key);
|
|
571
|
-
if (acc) {
|
|
572
|
-
const partial = stringValue(d?.partial_json) ?? "";
|
|
573
|
-
acc.argsRaw += partial;
|
|
574
|
-
toolCalls.set(key, acc);
|
|
575
|
-
}
|
|
576
|
-
} else {
|
|
577
|
-
const text = stringValue(d?.text);
|
|
578
|
-
if (text) yield {
|
|
579
|
-
type: "text_delta",
|
|
580
|
-
task: context.task,
|
|
581
|
-
session: context.session,
|
|
582
|
-
text,
|
|
583
|
-
timestamp: nowIso()
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (eventType === "content_block_stop") {
|
|
588
|
-
const key = `anthropic:${numberValue(parsed.index) ?? 0}`;
|
|
589
|
-
const acc = toolCalls.get(key);
|
|
590
|
-
if (acc) {
|
|
591
|
-
acc.finalized = true;
|
|
592
|
-
toolCalls.set(key, acc);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
for (const event of drainFinalizedToolCalls(toolCalls, context)) yield event;
|
|
596
|
-
const text = stringValue(delta?.content) ?? stringValue(message?.content) ?? stringValue(parsed.text);
|
|
597
|
-
if (text) {
|
|
598
|
-
yield {
|
|
599
|
-
type: "text_delta",
|
|
600
|
-
task: context.task,
|
|
601
|
-
session: context.session,
|
|
602
|
-
text,
|
|
603
|
-
timestamp: nowIso()
|
|
604
|
-
};
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
const mapped = mapCommonBackendEvent(parsed, context);
|
|
608
|
-
if (mapped) yield mapped;
|
|
609
|
-
}
|
|
610
|
-
function* drainFinalizedToolCalls(toolCalls, context) {
|
|
611
|
-
for (const [key, acc] of toolCalls) {
|
|
612
|
-
if (!acc.finalized) continue;
|
|
613
|
-
toolCalls.delete(key);
|
|
614
|
-
yield buildToolCallEvent(acc, context);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
function* flushPendingToolCalls(toolCalls, context) {
|
|
618
|
-
for (const [key, acc] of toolCalls) {
|
|
619
|
-
toolCalls.delete(key);
|
|
620
|
-
yield buildToolCallEvent(acc, context);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* The accumulator key one streamed `delta.tool_calls[]` entry belongs to.
|
|
625
|
-
*
|
|
626
|
-
* OpenAI's streaming contract puts `index` on every fragment, so fragments of
|
|
627
|
-
* the same call concatenate under `openai:<index>`. Some OpenAI-COMPATIBLE
|
|
628
|
-
* gateways do not: the Tangle router's Gemini lane emits N COMPLETE tool calls
|
|
629
|
-
* inside a single delta, each with a distinct `id` and no `index` at all.
|
|
630
|
-
* Defaulting those to index 0 concatenated every call's `arguments` into one
|
|
631
|
-
* string, which then failed `JSON.parse` and was surfaced as a raw string — so
|
|
632
|
-
* a turn that asked for six deliverables silently produced none.
|
|
633
|
-
*
|
|
634
|
-
* Resolution order:
|
|
635
|
-
* 1. `index` present → spec path, key by index (cross-delta fragments join).
|
|
636
|
-
* 2. `id` present → key by id; distinct ids are distinct calls, and a
|
|
637
|
-
* repeated id still concatenates correctly.
|
|
638
|
-
* 3. neither → an argument-only continuation fragment; append to the
|
|
639
|
-
* last-OPENED unfinalized OpenAI entry. `Map` iteration
|
|
640
|
-
* is insertion-ordered and `set` on an existing key does
|
|
641
|
-
* not reorder, so this is the most recently STARTED call
|
|
642
|
-
* still taking arguments — which is the one a gateway
|
|
643
|
-
* streaming calls sequentially is continuing.
|
|
644
|
-
*/
|
|
645
|
-
function deltaToolCallKey(rec, id, toolCalls) {
|
|
646
|
-
const idx = numberValue(rec.index);
|
|
647
|
-
if (idx !== void 0) return `openai:${idx}`;
|
|
648
|
-
if (id) return `openai:id:${id}`;
|
|
649
|
-
let last;
|
|
650
|
-
for (const [key, acc] of toolCalls) if (acc.source === "openai" && !acc.finalized) last = key;
|
|
651
|
-
return last ?? "openai:0";
|
|
652
|
-
}
|
|
653
|
-
function buildToolCallEvent(acc, context) {
|
|
654
|
-
let args = acc.argsRaw;
|
|
655
|
-
if (acc.argsRaw.length > 0) try {
|
|
656
|
-
args = JSON.parse(acc.argsRaw);
|
|
657
|
-
} catch {
|
|
658
|
-
args = acc.argsRaw;
|
|
659
|
-
}
|
|
660
|
-
else args = {};
|
|
661
|
-
return {
|
|
662
|
-
type: "tool_call",
|
|
663
|
-
task: context.task,
|
|
664
|
-
session: context.session,
|
|
665
|
-
toolName: acc.name ?? "tool",
|
|
666
|
-
toolCallId: acc.id,
|
|
667
|
-
args,
|
|
668
|
-
timestamp: nowIso()
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Accumulate token usage from any SSE chunk shape the router may emit.
|
|
673
|
-
*
|
|
674
|
-
* - OpenAI: a final chunk before `[DONE]` with `{ usage: { prompt_tokens,
|
|
675
|
-
* completion_tokens, total_tokens } }` and (often) empty `choices`. The
|
|
676
|
-
* `model` field is on every chunk and the last `choices[0].finish_reason`
|
|
677
|
-
* carries the stop reason.
|
|
678
|
-
* - Anthropic: `message_start` carries `message.model` and
|
|
679
|
-
* `message.usage.input_tokens`. The terminal `message_delta` carries
|
|
680
|
-
* `usage.output_tokens` and `delta.stop_reason`.
|
|
681
|
-
*/
|
|
682
|
-
function captureStreamUsage(parsed, usage) {
|
|
683
|
-
const model = stringValue(parsed.model);
|
|
684
|
-
if (model && !usage.model) usage.model = model;
|
|
685
|
-
const openAiUsage = parsed.usage;
|
|
686
|
-
if (openAiUsage && typeof openAiUsage === "object") {
|
|
687
|
-
const promptTokens = numberValue(openAiUsage.prompt_tokens);
|
|
688
|
-
const completionTokens = numberValue(openAiUsage.completion_tokens);
|
|
689
|
-
const inputTokens = numberValue(openAiUsage.input_tokens);
|
|
690
|
-
const outputTokens = numberValue(openAiUsage.output_tokens);
|
|
691
|
-
if (promptTokens !== void 0) {
|
|
692
|
-
usage.tokensIn = promptTokens;
|
|
693
|
-
usage.saw = true;
|
|
694
|
-
} else if (inputTokens !== void 0) {
|
|
695
|
-
usage.tokensIn = (usage.tokensIn ?? 0) + inputTokens;
|
|
696
|
-
usage.saw = true;
|
|
697
|
-
}
|
|
698
|
-
if (completionTokens !== void 0) {
|
|
699
|
-
usage.tokensOut = completionTokens;
|
|
700
|
-
usage.saw = true;
|
|
701
|
-
} else if (outputTokens !== void 0) {
|
|
702
|
-
usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
|
|
703
|
-
usage.saw = true;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
const type = stringValue(parsed.type);
|
|
707
|
-
if (type === "message_start") {
|
|
708
|
-
const message = parsed.message;
|
|
709
|
-
const messageModel = stringValue(message?.model);
|
|
710
|
-
if (messageModel && !usage.model) usage.model = messageModel;
|
|
711
|
-
const messageUsage = message?.usage;
|
|
712
|
-
const inputTokens = numberValue(messageUsage?.input_tokens);
|
|
713
|
-
if (inputTokens !== void 0) {
|
|
714
|
-
usage.tokensIn = inputTokens;
|
|
715
|
-
usage.saw = true;
|
|
716
|
-
}
|
|
717
|
-
const outputTokens = numberValue(messageUsage?.output_tokens);
|
|
718
|
-
if (outputTokens !== void 0) {
|
|
719
|
-
usage.tokensOut = (usage.tokensOut ?? 0) + outputTokens;
|
|
720
|
-
usage.saw = true;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
if (type === "message_delta") {
|
|
724
|
-
const delta = parsed.delta;
|
|
725
|
-
const stopReason = stringValue(delta?.stop_reason);
|
|
726
|
-
if (stopReason) usage.finishReason = stopReason;
|
|
727
|
-
}
|
|
728
|
-
const choices = parsed.choices;
|
|
729
|
-
if (Array.isArray(choices)) {
|
|
730
|
-
const finishReason = stringValue(choices[0]?.finish_reason);
|
|
731
|
-
if (finishReason) usage.finishReason = finishReason;
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
function numberValue(value) {
|
|
735
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
736
|
-
}
|
|
737
|
-
function stringValue(value) {
|
|
738
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
739
|
-
}
|
|
740
|
-
//#endregion
|
|
741
|
-
export { InMemoryRuntimeSessionStore as a, startOrResumeRuntimeSession as c, normalizeBackendStreamEvent as i, touchSession as l, createOpenAICompatibleBackend as n, newRuntimeSession as o, createSandboxPromptBackend as r, nowIso as s, createIterableBackend as t };
|
|
742
|
-
|
|
743
|
-
//# sourceMappingURL=backends-CiOCyRHb.js.map
|