@tangle-network/agent-app 0.5.1 → 0.7.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/dist/{chunk-DXAMBUDL.js → chunk-OLCVUGGI.js} +2 -2
- package/dist/{chunk-LT2YIMEB.js → chunk-QAQBR6KQ.js} +19 -5
- package/dist/chunk-QAQBR6KQ.js.map +1 -0
- package/dist/{chunk-GMFPCCQZ.js → chunk-SDOT7RNB.js} +152 -2
- package/dist/chunk-SDOT7RNB.js.map +1 -0
- package/dist/{chunk-7PXRACS2.js → chunk-TH2AOJJM.js} +174 -2
- package/dist/chunk-TH2AOJJM.js.map +1 -0
- package/dist/eval/index.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +24 -4
- package/dist/model-catalog-BEAEVDaa.d.ts +85 -0
- package/dist/preset-cloudflare/index.d.ts +1 -1
- package/dist/runtime/index.d.ts +23 -3
- package/dist/runtime/index.js +10 -2
- package/dist/stream/index.d.ts +88 -1
- package/dist/stream/index.js +13 -1
- package/dist/tools/index.d.ts +10 -2
- package/dist/tools/index.js +2 -2
- package/dist/{types-CeWor4bQ.d.ts → types-By4B3K37.d.ts} +12 -1
- package/dist/web-react/index.d.ts +117 -0
- package/dist/web-react/index.js +361 -0
- package/dist/web-react/index.js.map +1 -0
- package/package.json +14 -3
- package/dist/chunk-7PXRACS2.js.map +0 -1
- package/dist/chunk-GMFPCCQZ.js.map +0 -1
- package/dist/chunk-LT2YIMEB.js.map +0 -1
- /package/dist/{chunk-DXAMBUDL.js.map → chunk-OLCVUGGI.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dispatchAppTool,
|
|
3
3
|
outcomeStatus
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QAQBR6KQ.js";
|
|
5
5
|
|
|
6
6
|
// src/tools/capability.ts
|
|
7
7
|
async function createCapabilityToken(userId, opts) {
|
|
@@ -134,4 +134,4 @@ export {
|
|
|
134
134
|
buildHttpMcpServer,
|
|
135
135
|
buildAppToolMcpServer
|
|
136
136
|
};
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
137
|
+
//# sourceMappingURL=chunk-OLCVUGGI.js.map
|
|
@@ -97,10 +97,24 @@ async function dispatchAppTool(toolName, rawArgs, ctx, opts) {
|
|
|
97
97
|
}
|
|
98
98
|
if (!title) return { ok: false, code: "missing_title", message: "title is required." };
|
|
99
99
|
const description = rawArgs.description == null ? null : String(rawArgs.description);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
let regulated = opts.taxonomy.regulatedTypes.includes(type);
|
|
101
|
+
if (opts.needsApproval) {
|
|
102
|
+
try {
|
|
103
|
+
regulated = await opts.needsApproval(type, { title, description }, ctx);
|
|
104
|
+
} catch {
|
|
105
|
+
regulated = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const r2 = await opts.handlers.submitProposal({ type, title, description, regulated }, ctx);
|
|
109
|
+
const { proposalId, deduped, status, ...extra } = r2;
|
|
110
|
+
const effectiveStatus = status ?? "queued_for_approval";
|
|
111
|
+
opts.onProduced?.({
|
|
112
|
+
type: "proposal_created",
|
|
113
|
+
proposalId,
|
|
114
|
+
title,
|
|
115
|
+
status: effectiveStatus === "executed" ? "executed" : "pending"
|
|
116
|
+
});
|
|
117
|
+
return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } };
|
|
104
118
|
}
|
|
105
119
|
if (toolName === "schedule_followup") {
|
|
106
120
|
const r2 = await opts.handlers.scheduleFollowup(
|
|
@@ -142,4 +156,4 @@ export {
|
|
|
142
156
|
outcomeStatus,
|
|
143
157
|
createAppToolRuntimeExecutor
|
|
144
158
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
159
|
+
//# sourceMappingURL=chunk-QAQBR6KQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/errors.ts","../src/tools/openai.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts"],"sourcesContent":["/** A correctable bad-input error a tool handler throws; the HTTP layer maps it\n * to a 4xx with the code, the runtime layer to a failed tool_result. So the\n * agent learns the call failed and can correct, instead of a silent success. */\nexport class ToolInputError extends Error {\n constructor(\n public code: string,\n message: string,\n public status = 400,\n ) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n","import type { AppToolTaxonomy } from './types'\n\n/** The four canonical app-tool names. Stable identifiers the model calls in\n * both the sandbox (MCP server name) and runtime (function-tool name) paths. */\nexport const APP_TOOL_NAMES = ['submit_proposal', 'schedule_followup', 'render_ui', 'add_citation'] as const\nexport type AppToolName = (typeof APP_TOOL_NAMES)[number]\n\nconst NAME_SET = new Set<string>(APP_TOOL_NAMES)\nexport function isAppToolName(name: string): name is AppToolName {\n return NAME_SET.has(name)\n}\n\n/** A minimal OpenAI Chat Completions function-tool shape — structurally\n * compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without\n * importing it (keeps this package runtime-free). */\nexport interface OpenAIFunctionTool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record<string, unknown>\n }\n}\n\n/**\n * Build the four app tools in OpenAI function-tool shape. `submit_proposal`'s\n * `type` enum is the product's {@link AppToolTaxonomy.proposalTypes}; the other\n * three are fixed. Pass the result to the agent-runtime backend's `tools`.\n */\nexport function buildAppToolOpenAITools(taxonomy: AppToolTaxonomy): OpenAIFunctionTool[] {\n return [\n {\n type: 'function',\n function: {\n name: 'submit_proposal',\n description:\n 'Route a regulated or state-changing action to a human for approval (a recommendation, contacting/soliciting a contact, outreach, a record/account change, scheduling). Queues it for a named certified human to approve before it executes.',\n parameters: {\n type: 'object',\n properties: {\n type: { type: 'string', enum: [...taxonomy.proposalTypes] },\n title: { type: 'string', description: 'Short label for the approval queue.' },\n description: { type: 'string', description: 'The full drafted message/recommendation, with sources.' },\n },\n required: ['type', 'title'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'schedule_followup',\n description: 'Register a dated cadence step (a reminder, chase, or check-in) on the follow-up calendar. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n dueDate: { type: 'string', description: 'ISO date YYYY-MM-DD.' },\n priority: { type: 'string', enum: ['low', 'medium', 'high'] },\n },\n required: ['title', 'dueDate'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'render_ui',\n description: 'Show a generated view live in the workspace. Validates the OpenUI JSON and persists the artifact. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n title: { type: 'string' },\n schema: { type: 'object', description: 'The OpenUI JSON object.' },\n },\n required: ['title', 'schema'],\n },\n },\n },\n {\n type: 'function',\n function: {\n name: 'add_citation',\n description: 'Anchor a grounding reference: the exact quote from a file backing a figure or claim. Verifies the quote appears in the file. Executes immediately.',\n parameters: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'The vault file path.' },\n quote: { type: 'string', description: 'The exact text from it.' },\n },\n required: ['path', 'quote'],\n },\n },\n },\n ]\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Per-call approval policy. When provided it OVERRIDES the static\n * `taxonomy.regulatedTypes` membership check, so products can gate by\n * cost threshold, environment, or first-use instead of always/never.\n * Fail-closed: a predicate that throws counts as \"approval required\". */\n needsApproval?: (type: string, args: { title: string; description: string | null }, ctx: AppToolContext) => boolean | Promise<boolean>\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n // Approval policy runs BEFORE the handler so the decision can gate the\n // side effect itself, not merely re-label it afterwards.\n let regulated = opts.taxonomy.regulatedTypes.includes(type)\n if (opts.needsApproval) {\n try {\n regulated = await opts.needsApproval(type, { title, description }, ctx)\n } catch {\n regulated = true // fail-closed: a broken policy means approval required\n }\n }\n const r = await opts.handlers.submitProposal({ type, title, description, regulated }, ctx)\n // Pass the handler's result through: products with immediate-execute\n // proposal types return status 'executed' plus their own fields\n // (e.g. datasetId) — the model must see what actually happened, not a\n // hard-coded \"queued for approval\".\n const { proposalId, deduped, status, ...extra } = r\n const effectiveStatus = status ?? 'queued_for_approval'\n opts.onProduced?.({\n type: 'proposal_created',\n proposalId,\n title,\n status: effectiveStatus === 'executed' ? 'executed' : 'pending',\n })\n return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n"],"mappings":"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","names":["r"]}
|
|
@@ -225,6 +225,150 @@ function countUserMessages(messages) {
|
|
|
225
225
|
return messages.filter((message) => message.role === "user").length;
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// src/stream/turn-buffer.ts
|
|
229
|
+
function deltaTypeOf(ev) {
|
|
230
|
+
const e = ev;
|
|
231
|
+
if (!e || typeof e !== "object") return null;
|
|
232
|
+
const inner = e.kind === "event" ? e.event : e;
|
|
233
|
+
if (!inner || typeof inner !== "object") return null;
|
|
234
|
+
if ((inner.type === "text" || inner.type === "reasoning") && typeof inner.text === "string") {
|
|
235
|
+
return inner.type;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
function coalesceDeltas(events) {
|
|
240
|
+
const out = [];
|
|
241
|
+
for (const ev of events) {
|
|
242
|
+
const type = deltaTypeOf(ev);
|
|
243
|
+
const prev = out[out.length - 1];
|
|
244
|
+
if (type && prev && deltaTypeOf(prev) === type) {
|
|
245
|
+
const read = (x) => x.kind === "event" ? x.event : x;
|
|
246
|
+
const merged = JSON.parse(JSON.stringify(prev));
|
|
247
|
+
read(merged).text = String(read(prev).text) + String(read(ev).text);
|
|
248
|
+
out[out.length - 1] = merged;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
out.push(ev);
|
|
252
|
+
}
|
|
253
|
+
return out;
|
|
254
|
+
}
|
|
255
|
+
async function pumpBufferedTurn(opts) {
|
|
256
|
+
const flushIntervalMs = opts.flushIntervalMs ?? 400;
|
|
257
|
+
let seq = 0;
|
|
258
|
+
let clientGone = false;
|
|
259
|
+
let pending = [];
|
|
260
|
+
let lastFlush = Date.now();
|
|
261
|
+
async function flush() {
|
|
262
|
+
if (pending.length === 0) return;
|
|
263
|
+
const batch = coalesceDeltas(pending);
|
|
264
|
+
pending = [];
|
|
265
|
+
const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }));
|
|
266
|
+
await opts.store.append(opts.turnId, rows);
|
|
267
|
+
lastFlush = Date.now();
|
|
268
|
+
}
|
|
269
|
+
await opts.store.setStatus(opts.turnId, "running");
|
|
270
|
+
try {
|
|
271
|
+
for await (const ev of opts.source) {
|
|
272
|
+
pending.push(ev);
|
|
273
|
+
if (!clientGone && opts.write) {
|
|
274
|
+
try {
|
|
275
|
+
await opts.write(JSON.stringify(ev));
|
|
276
|
+
} catch {
|
|
277
|
+
clientGone = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (Date.now() - lastFlush >= flushIntervalMs) await flush();
|
|
281
|
+
}
|
|
282
|
+
await flush();
|
|
283
|
+
await opts.store.setStatus(opts.turnId, "complete");
|
|
284
|
+
} catch (err) {
|
|
285
|
+
await flush().catch(() => {
|
|
286
|
+
});
|
|
287
|
+
await opts.store.setStatus(opts.turnId, "error").catch(() => {
|
|
288
|
+
});
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
async function* replayTurnEvents(opts) {
|
|
293
|
+
const pollMs = opts.pollMs ?? 500;
|
|
294
|
+
const timeoutMs = opts.timeoutMs ?? 12e4;
|
|
295
|
+
let cursor = opts.fromSeq ?? 0;
|
|
296
|
+
const deadline = Date.now() + timeoutMs;
|
|
297
|
+
for (; ; ) {
|
|
298
|
+
const batch = await opts.store.read(opts.turnId, cursor);
|
|
299
|
+
for (const row of batch) {
|
|
300
|
+
cursor = Math.max(cursor, row.seq);
|
|
301
|
+
yield row;
|
|
302
|
+
}
|
|
303
|
+
const status = await opts.store.getStatus(opts.turnId);
|
|
304
|
+
if (status !== "running") {
|
|
305
|
+
yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: status ?? "unknown" }) };
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (Date.now() >= deadline) {
|
|
309
|
+
yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: "timeout" }) };
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
var TURN_EVENTS_MIGRATION_SQL = `
|
|
316
|
+
CREATE TABLE IF NOT EXISTS turn_events (
|
|
317
|
+
turnId TEXT NOT NULL,
|
|
318
|
+
seq INTEGER NOT NULL,
|
|
319
|
+
event TEXT NOT NULL,
|
|
320
|
+
PRIMARY KEY (turnId, seq)
|
|
321
|
+
);
|
|
322
|
+
CREATE TABLE IF NOT EXISTS turn_status (
|
|
323
|
+
turnId TEXT PRIMARY KEY,
|
|
324
|
+
status TEXT NOT NULL,
|
|
325
|
+
updatedAt TEXT NOT NULL
|
|
326
|
+
);
|
|
327
|
+
`;
|
|
328
|
+
function createD1TurnEventStore(db) {
|
|
329
|
+
return {
|
|
330
|
+
async append(turnId, events) {
|
|
331
|
+
if (!events.length) return;
|
|
332
|
+
const placeholders = events.map(() => "(?, ?, ?)").join(", ");
|
|
333
|
+
const values = events.flatMap((e) => [turnId, e.seq, e.event]);
|
|
334
|
+
await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run();
|
|
335
|
+
},
|
|
336
|
+
async read(turnId, fromSeq) {
|
|
337
|
+
const { results } = await db.prepare("SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC").bind(turnId, fromSeq).all();
|
|
338
|
+
return results;
|
|
339
|
+
},
|
|
340
|
+
async setStatus(turnId, status) {
|
|
341
|
+
await db.prepare(
|
|
342
|
+
"INSERT INTO turn_status (turnId, status, updatedAt) VALUES (?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, updatedAt = excluded.updatedAt"
|
|
343
|
+
).bind(turnId, status, (/* @__PURE__ */ new Date()).toISOString()).run();
|
|
344
|
+
},
|
|
345
|
+
async getStatus(turnId) {
|
|
346
|
+
const row = await db.prepare("SELECT status FROM turn_status WHERE turnId = ?").bind(turnId).first();
|
|
347
|
+
return row?.status ?? null;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function createMemoryTurnEventStore() {
|
|
352
|
+
const events = /* @__PURE__ */ new Map();
|
|
353
|
+
const status = /* @__PURE__ */ new Map();
|
|
354
|
+
return {
|
|
355
|
+
async append(turnId, rows) {
|
|
356
|
+
const list = events.get(turnId) ?? [];
|
|
357
|
+
list.push(...rows);
|
|
358
|
+
events.set(turnId, list);
|
|
359
|
+
},
|
|
360
|
+
async read(turnId, fromSeq) {
|
|
361
|
+
return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq);
|
|
362
|
+
},
|
|
363
|
+
async setStatus(turnId, s) {
|
|
364
|
+
status.set(turnId, s);
|
|
365
|
+
},
|
|
366
|
+
async getStatus(turnId) {
|
|
367
|
+
return status.get(turnId) ?? null;
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
228
372
|
export {
|
|
229
373
|
asRecord,
|
|
230
374
|
asString,
|
|
@@ -240,6 +384,12 @@ export {
|
|
|
240
384
|
normalizeClientTurnId,
|
|
241
385
|
buildUserTextParts,
|
|
242
386
|
messageHasTurnId,
|
|
243
|
-
resolveChatTurn
|
|
387
|
+
resolveChatTurn,
|
|
388
|
+
coalesceDeltas,
|
|
389
|
+
pumpBufferedTurn,
|
|
390
|
+
replayTurnEvents,
|
|
391
|
+
TURN_EVENTS_MIGRATION_SQL,
|
|
392
|
+
createD1TurnEventStore,
|
|
393
|
+
createMemoryTurnEventStore
|
|
244
394
|
};
|
|
245
|
-
//# sourceMappingURL=chunk-
|
|
395
|
+
//# sourceMappingURL=chunk-SDOT7RNB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stream/stream-normalizer.ts","../src/stream/turn-identity.ts","../src/stream/turn-buffer.ts"],"sourcesContent":["export type JsonRecord = Record<string, unknown>\n\nexport interface StreamEvent {\n type: string\n data?: JsonRecord\n}\n\nexport function asRecord(value: unknown): JsonRecord | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as JsonRecord\n : undefined\n}\n\nexport function asString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nexport function resolveToolId(part: JsonRecord): string {\n return String(\n part.id ??\n part.callID ??\n part.callId ??\n part.toolUseId ??\n part.toolCallId ??\n part.tool ??\n part.name ??\n `tool-${Date.now()}`,\n )\n}\n\nexport function resolveToolName(part: JsonRecord): string {\n return String(part.tool ?? part.name ?? 'tool')\n}\n\nexport function normalizeTime(value: unknown): JsonRecord | undefined {\n const record = asRecord(value)\n if (!record) return undefined\n\n const start = Number(record.start ?? record.startedAt ?? record.started_at)\n const end = Number(record.end ?? record.completedAt ?? record.completed_at)\n if (!Number.isFinite(start) && !Number.isFinite(end)) return undefined\n\n return {\n start: Number.isFinite(start) ? start : undefined,\n end: Number.isFinite(end) ? end : undefined,\n }\n}\n\nexport function normalizeToolEvent(event: StreamEvent): StreamEvent {\n if (event.type === 'tool_call' || event.type === 'tool.call') {\n const data = event.data ?? {}\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n input: data.arguments ?? data.input,\n status: 'running',\n },\n },\n }\n }\n\n if (event.type === 'tool_result' || event.type === 'tool.result') {\n const data = event.data ?? {}\n const error = asString(data.error)\n return {\n type: 'message.part.updated',\n data: {\n part: {\n type: 'tool',\n id: data.id ?? data.callId ?? data.callID ?? data.name,\n tool: data.name ?? data.tool ?? 'tool',\n output: data.output,\n error,\n status: error ? 'error' : 'completed',\n },\n },\n }\n }\n\n return event\n}\n\nexport function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null {\n const type = String(rawPart.type ?? '')\n\n if (type === 'text') {\n return {\n type: 'text',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n }\n }\n\n if (type === 'reasoning') {\n return {\n type: 'reasoning',\n text: asString(rawPart.text) ?? asString(rawPart.content) ?? '',\n time: normalizeTime(rawPart.time),\n }\n }\n\n if (type === 'tool') {\n const state = asRecord(rawPart.state)\n const output = state?.output ?? rawPart.output\n const error = asString(state?.error ?? rawPart.error)\n const status =\n state?.status === 'completed' || rawPart.status === 'completed'\n ? 'completed'\n : state?.status === 'error' || rawPart.status === 'error' || error\n ? 'error'\n : output !== undefined\n ? 'completed'\n : 'running'\n\n return {\n type: 'tool',\n id: resolveToolId(rawPart),\n tool: resolveToolName(rawPart),\n callID:\n rawPart.callID != null || rawPart.callId != null\n ? String(rawPart.callID ?? rawPart.callId)\n : undefined,\n state: {\n status,\n input: state?.input ?? rawPart.input,\n output,\n error,\n metadata: asRecord(state?.metadata) ?? asRecord(rawPart.metadata),\n time: normalizeTime(state?.time ?? rawPart.time),\n },\n }\n }\n\n return null\n}\n\nexport function getPartKey(part: JsonRecord): string {\n const type = String(part.type ?? 'unknown')\n if (type === 'tool') {\n return `tool:${resolveToolId(part)}`\n }\n\n if (type === 'reasoning') {\n return `reasoning:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n }\n\n return `text:${String(part.id ?? part.partId ?? part.index ?? 'current')}`\n}\n\nexport function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord {\n const type = String(incoming.type ?? '')\n if (!existing) {\n if (type === 'text' && delta) {\n return { type: 'text', text: delta }\n }\n return incoming\n }\n\n if (type === 'text' && String(existing.type ?? '') === 'text') {\n return {\n ...existing,\n ...incoming,\n text: delta ? `${String(existing.text ?? '')}${delta}` : String(incoming.text ?? ''),\n }\n }\n\n if (type === 'reasoning' && String(existing.type ?? '') === 'reasoning') {\n const existingText = String(existing.text ?? '')\n const incomingText = String(incoming.text ?? '')\n return {\n ...existing,\n ...incoming,\n text: delta && incomingText === existingText ? `${existingText}${delta}` : incomingText || existingText,\n time: incoming.time ?? existing.time,\n }\n }\n\n if (type === 'tool' && String(existing.type ?? '') === 'tool') {\n return {\n ...existing,\n ...incoming,\n state: {\n ...(asRecord(existing.state) ?? {}),\n ...(asRecord(incoming.state) ?? {}),\n time: asRecord(incoming.state)?.time ?? asRecord(existing.state)?.time,\n },\n }\n }\n\n return incoming\n}\n\nexport function finalizeAssistantParts(\n partOrder: string[],\n partMap: Map<string, JsonRecord>,\n finalText: string,\n): JsonRecord[] {\n const parts = partOrder\n .map((key) => partMap.get(key))\n .filter((part): part is JsonRecord => Boolean(part))\n\n if (!parts.some((part) => String(part.type ?? '') === 'text')) {\n if (finalText.trim()) {\n parts.push({ type: 'text', text: finalText })\n }\n return parts\n }\n\n return parts.map((part) => {\n if (String(part.type ?? '') !== 'text') return part\n return {\n ...part,\n text: finalText || String(part.text ?? ''),\n }\n })\n}\n\nexport function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array {\n return encoder.encode(`${JSON.stringify(event)}\\n`)\n}\n","import type { JsonRecord } from './stream-normalizer'\n\nexport interface PersistedChatMessageForTurn {\n id: string\n role: 'user' | 'assistant' | 'system' | 'tool'\n content: string\n parts: Array<Record<string, unknown>> | null\n}\n\nexport interface ResolvedChatTurn {\n turnIndex: number\n shouldInsertUserMessage: boolean\n priorMessages: PersistedChatMessageForTurn[]\n userParts: JsonRecord[]\n}\n\nexport function normalizeClientTurnId(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'string') throw new Error('turnId must be a string')\n const trimmed = value.trim()\n if (!trimmed) throw new Error('turnId must not be blank')\n if (trimmed.length > 160) throw new Error('turnId is too long')\n if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {\n throw new Error('turnId contains unsupported characters')\n }\n return trimmed\n}\n\nexport function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[] {\n const part: JsonRecord = { type: 'text', text }\n if (turnId) part.turnId = turnId\n return [part]\n}\n\nexport function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean {\n for (const part of message.parts ?? []) {\n if (part && typeof part === 'object' && String(part.turnId ?? '') === turnId) {\n return true\n }\n }\n return false\n}\n\nexport function resolveChatTurn(input: {\n existingMessages: PersistedChatMessageForTurn[]\n userContent: string\n turnId?: string\n}): ResolvedChatTurn {\n const { existingMessages, userContent, turnId } = input\n const reusableIndex = findReusableUserMessageIndex(existingMessages, userContent, turnId)\n if (reusableIndex >= 0) {\n return {\n turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),\n shouldInsertUserMessage: false,\n priorMessages: existingMessages.slice(0, reusableIndex),\n userParts: buildUserTextParts(userContent, turnId),\n }\n }\n\n return {\n turnIndex: countUserMessages(existingMessages),\n shouldInsertUserMessage: true,\n priorMessages: existingMessages,\n userParts: buildUserTextParts(userContent, turnId),\n }\n}\n\nfunction findReusableUserMessageIndex(\n messages: PersistedChatMessageForTurn[],\n userContent: string,\n turnId: string | undefined,\n): number {\n if (turnId) {\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const message = messages[index]\n if (message?.role === 'user' && messageHasTurnId(message, turnId)) return index\n }\n }\n\n const latest = messages.at(-1)\n if (latest?.role === 'user' && latest.content === userContent) {\n return messages.length - 1\n }\n\n return -1\n}\n\nfunction countUserMessages(messages: PersistedChatMessageForTurn[]): number {\n return messages.filter((message) => message.role === 'user').length\n}\n","/**\n * Resumable chat turns — the router-path answer to \"streams resume on\n * disconnect\" (issue #27). A turn's loop events are teed into a store as they\n * stream; the turn keeps running under `ctx.waitUntil` when the client drops;\n * a reconnecting client replays the buffered tail by sequence number and\n * keeps following until the turn completes.\n *\n * POST /chat/stream → pumpBufferedTurn(...) + live NDJSON\n * GET /chat/stream/:turnId → replayTurnEvents({ fromSeq }) → NDJSON\n *\n * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation\n * ships here because that's what Cloudflare products have (KV is unsuitable:\n * eventually consistent cross-isolate). Per-token deltas would mean hundreds\n * of rows per turn, so consecutive text/reasoning deltas are coalesced within\n * a flush window before they are persisted — replay yields slightly chunkier\n * deltas with identical concatenation.\n */\n\nexport type TurnStatus = 'running' | 'complete' | 'error'\n\nexport interface BufferedTurnEvent {\n seq: number\n /** The serialized event line (JSON string, no trailing newline). */\n event: string\n}\n\nexport interface TurnEventStore {\n append(turnId: string, events: BufferedTurnEvent[]): Promise<void>\n read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>\n setStatus(turnId: string, status: TurnStatus): Promise<void>\n getStatus(turnId: string): Promise<TurnStatus | null>\n}\n\n// ── coalescing ────────────────────────────────────────────────────────────\n\ntype AnyRecord = Record<string, unknown>\n\nfunction deltaTypeOf(ev: unknown): 'text' | 'reasoning' | null {\n const e = ev as AnyRecord | null\n if (!e || typeof e !== 'object') return null\n const inner = (e.kind === 'event' ? (e.event as AnyRecord | undefined) : e) as AnyRecord | undefined\n if (!inner || typeof inner !== 'object') return null\n if ((inner.type === 'text' || inner.type === 'reasoning') && typeof inner.text === 'string') {\n return inner.type\n }\n return null\n}\n\n/** Merge consecutive text/reasoning deltas of the same type into one event.\n * Concatenation-preserving: replaying the coalesced stream produces the same\n * accumulated text as the original. */\nexport function coalesceDeltas(events: unknown[]): unknown[] {\n const out: unknown[] = []\n for (const ev of events) {\n const type = deltaTypeOf(ev)\n const prev = out[out.length - 1]\n if (type && prev && deltaTypeOf(prev) === type) {\n const read = (x: unknown): AnyRecord =>\n ((x as AnyRecord).kind === 'event' ? (x as AnyRecord).event : x) as AnyRecord\n const merged = JSON.parse(JSON.stringify(prev)) as AnyRecord\n read(merged).text = String(read(prev).text) + String(read(ev).text)\n out[out.length - 1] = merged\n continue\n }\n out.push(ev)\n }\n return out\n}\n\n// ── pump (producer side) ──────────────────────────────────────────────────\n\nexport interface PumpBufferedTurnOptions {\n source: AsyncIterable<unknown>\n store: TurnEventStore\n turnId: string\n /** Deliver one serialized line (with seq) to the live client. Throwing here\n * (client disconnected) does NOT stop the turn — events keep buffering. */\n write?: (line: string) => Promise<void> | void\n /** Flush buffered events to the store at most this often. Default 400ms. */\n flushIntervalMs?: number\n}\n\n/**\n * Drive a turn to completion regardless of the live client: every source\n * event is sequence-numbered, delivered to `write` (best-effort), and flushed\n * to the store in coalesced batches. Returns a promise that resolves when the\n * turn finishes — hand it to `ctx.waitUntil` so a disconnect can't kill the\n * turn. Never rejects on client-write failure; a source error marks the turn\n * status 'error' (after flushing what was produced) and rethrows.\n */\nexport async function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void> {\n const flushIntervalMs = opts.flushIntervalMs ?? 400\n let seq = 0\n let clientGone = false\n let pending: unknown[] = []\n let lastFlush = Date.now()\n\n async function flush(): Promise<void> {\n if (pending.length === 0) return\n const batch = coalesceDeltas(pending)\n pending = []\n const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }))\n await opts.store.append(opts.turnId, rows)\n lastFlush = Date.now()\n }\n\n await opts.store.setStatus(opts.turnId, 'running')\n try {\n for await (const ev of opts.source) {\n pending.push(ev)\n if (!clientGone && opts.write) {\n try {\n // Live delivery carries a provisional ordering hint, not the\n // persisted seq (coalescing changes seq assignment); clients resume\n // with the seqs from replay, or 0 for \"everything\".\n await opts.write(JSON.stringify(ev))\n } catch {\n clientGone = true\n }\n }\n if (Date.now() - lastFlush >= flushIntervalMs) await flush()\n }\n await flush()\n await opts.store.setStatus(opts.turnId, 'complete')\n } catch (err) {\n await flush().catch(() => {})\n await opts.store.setStatus(opts.turnId, 'error').catch(() => {})\n throw err\n }\n}\n\n// ── replay (consumer side) ────────────────────────────────────────────────\n\nexport interface ReplayTurnEventsOptions {\n store: TurnEventStore\n turnId: string\n /** Replay strictly after this sequence number (0 = from the beginning). */\n fromSeq?: number\n /** Poll cadence while the turn is still running. Default 500ms. */\n pollMs?: number\n /** Give up following a 'running' turn after this long. Default 120s. */\n timeoutMs?: number\n}\n\n/**\n * Yield buffered events after `fromSeq`, then keep polling while the turn is\n * still 'running' until it completes, errors, or times out. Terminates with a\n * final `{seq: -1, event: '{\"type\":\"turn_status\",...}'}` marker so clients\n * know why the replay ended.\n */\nexport async function* replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent> {\n const pollMs = opts.pollMs ?? 500\n const timeoutMs = opts.timeoutMs ?? 120_000\n let cursor = opts.fromSeq ?? 0\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n const batch = await opts.store.read(opts.turnId, cursor)\n for (const row of batch) {\n cursor = Math.max(cursor, row.seq)\n yield row\n }\n const status = await opts.store.getStatus(opts.turnId)\n if (status !== 'running') {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: status ?? 'unknown' }) }\n return\n }\n if (Date.now() >= deadline) {\n yield { seq: -1, event: JSON.stringify({ type: 'turn_status', status: 'timeout' }) }\n return\n }\n await new Promise((r) => setTimeout(r, pollMs))\n }\n}\n\n// ── D1 store ──────────────────────────────────────────────────────────────\n\n/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */\nexport interface D1LikeForTurns {\n prepare(sql: string): {\n bind(...values: unknown[]): {\n run(): Promise<unknown>\n all<T = Record<string, unknown>>(): Promise<{ results: T[] }>\n first<T = Record<string, unknown>>(): Promise<T | null>\n }\n }\n}\n\n/** Schema for the D1 store — append to the product's migrations. */\nexport const TURN_EVENTS_MIGRATION_SQL = `\nCREATE TABLE IF NOT EXISTS turn_events (\n turnId TEXT NOT NULL,\n seq INTEGER NOT NULL,\n event TEXT NOT NULL,\n PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n turnId TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n updatedAt TEXT NOT NULL\n);\n`\n\nexport function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore {\n return {\n async append(turnId, events) {\n if (!events.length) return\n // One multi-row insert per flush window keeps write volume bounded.\n const placeholders = events.map(() => '(?, ?, ?)').join(', ')\n const values = events.flatMap((e) => [turnId, e.seq, e.event])\n await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run()\n },\n async read(turnId, fromSeq) {\n const { results } = await db\n .prepare('SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC')\n .bind(turnId, fromSeq)\n .all<{ seq: number; event: string }>()\n return results\n },\n async setStatus(turnId, status) {\n await db\n .prepare(\n 'INSERT INTO turn_status (turnId, status, updatedAt) VALUES (?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, updatedAt = excluded.updatedAt',\n )\n .bind(turnId, status, new Date().toISOString())\n .run()\n },\n async getStatus(turnId) {\n const row = await db.prepare('SELECT status FROM turn_status WHERE turnId = ?').bind(turnId).first<{ status: TurnStatus }>()\n return row?.status ?? null\n },\n }\n}\n\n/** In-memory store for tests and keyless local dev. */\nexport function createMemoryTurnEventStore(): TurnEventStore {\n const events = new Map<string, BufferedTurnEvent[]>()\n const status = new Map<string, TurnStatus>()\n return {\n async append(turnId, rows) {\n const list = events.get(turnId) ?? []\n list.push(...rows)\n events.set(turnId, list)\n },\n async read(turnId, fromSeq) {\n return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq)\n },\n async setStatus(turnId, s) {\n status.set(turnId, s)\n },\n async getStatus(turnId) {\n return status.get(turnId) ?? null\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,16 +2,183 @@ import {
|
|
|
2
2
|
buildAppToolOpenAITools,
|
|
3
3
|
createAppToolRuntimeExecutor,
|
|
4
4
|
isAppToolName
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QAQBR6KQ.js";
|
|
6
|
+
|
|
7
|
+
// src/runtime/model-catalog.ts
|
|
8
|
+
var PROVIDER_TIER = [
|
|
9
|
+
"anthropic",
|
|
10
|
+
"openai",
|
|
11
|
+
"google",
|
|
12
|
+
"xai",
|
|
13
|
+
"deepseek",
|
|
14
|
+
"moonshotai",
|
|
15
|
+
"moonshot",
|
|
16
|
+
"zai",
|
|
17
|
+
"z-ai",
|
|
18
|
+
"mistral",
|
|
19
|
+
"groq",
|
|
20
|
+
"nvidia",
|
|
21
|
+
"cohere",
|
|
22
|
+
"cerebras"
|
|
23
|
+
];
|
|
24
|
+
var EXCLUDED_ID = /(embedding|tts|transcribe|whisper|audio|realtime|image|lyria|sora|dall-e|moderation|content-safety|search-preview|search-api|deep-research)/;
|
|
25
|
+
var FEATURED_RULES = [
|
|
26
|
+
{ providers: ["anthropic"], match: /^claude-sonnet-[\d-]+$/ },
|
|
27
|
+
{ providers: ["anthropic"], match: /^claude-opus-[\d-]+$/ },
|
|
28
|
+
{ providers: ["anthropic"], match: /^claude-haiku-[\d-]+$/ },
|
|
29
|
+
{ providers: ["openai"], match: /^gpt-\d+(\.\d+)?$/ },
|
|
30
|
+
{ providers: ["openai"], match: /^gpt-\d+(\.\d+)?-mini$/ },
|
|
31
|
+
{ providers: ["google"], match: /^gemini-[\d.]+-pro(-preview)?$/ },
|
|
32
|
+
{ providers: ["google"], match: /^gemini-[\d.]+-flash(-preview)?$/ },
|
|
33
|
+
{ providers: ["xai"], match: /^grok-[\d.]+$/ },
|
|
34
|
+
{ providers: ["deepseek"], match: /^deepseek-(chat|v[\d.]+(-\w+)?)$/ },
|
|
35
|
+
{ providers: ["moonshotai", "moonshot"], match: /^kimi-k[\d.]+$/ },
|
|
36
|
+
{ providers: ["zai", "z-ai"], match: /^glm-[\d.]+$/ },
|
|
37
|
+
{ providers: ["mistral"], match: /^mistral-(large|medium)-?[\d.-]*$/ }
|
|
38
|
+
];
|
|
39
|
+
var TOOL_CAPABLE_FAMILY = /^(claude|gpt-[45]|gpt-oss|o[134]|gemini|grok|deepseek|glm|kimi|mistral|ministral|magistral|command|nemotron|llama)/;
|
|
40
|
+
function normalizeModelId(id) {
|
|
41
|
+
let tail = id.split("/").pop() ?? id;
|
|
42
|
+
tail = tail.replace(/:free$/, "");
|
|
43
|
+
tail = tail.replace(/-\d{8}$/, "");
|
|
44
|
+
tail = tail.replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
45
|
+
return tail;
|
|
46
|
+
}
|
|
47
|
+
function versionOf(normId) {
|
|
48
|
+
return (normId.match(/\d+/g) ?? []).map(Number);
|
|
49
|
+
}
|
|
50
|
+
function compareVersions(a, b) {
|
|
51
|
+
const len = Math.max(a.length, b.length);
|
|
52
|
+
for (let i = 0; i < len; i++) {
|
|
53
|
+
const d = (a[i] ?? -1) - (b[i] ?? -1);
|
|
54
|
+
if (d !== 0) return d;
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
function aliasPenalty(id) {
|
|
59
|
+
let p = 0;
|
|
60
|
+
if (id.includes("/")) p += 4;
|
|
61
|
+
if (/-\d{8}$|-\d{4}-\d{2}-\d{2}$/.test(id.replace(/:free$/, ""))) p += 2;
|
|
62
|
+
if (id.endsWith(":free")) p += 1;
|
|
63
|
+
return p;
|
|
64
|
+
}
|
|
65
|
+
function providerRank(provider) {
|
|
66
|
+
const i = PROVIDER_TIER.indexOf(provider);
|
|
67
|
+
return i === -1 ? PROVIDER_TIER.length : i;
|
|
68
|
+
}
|
|
69
|
+
function isChatModel(m) {
|
|
70
|
+
const arch = m.architecture;
|
|
71
|
+
if (!arch?.input_modalities || !arch?.output_modalities) return true;
|
|
72
|
+
return arch.input_modalities.includes("text") && arch.output_modalities.includes("text");
|
|
73
|
+
}
|
|
74
|
+
function isRouteable(m) {
|
|
75
|
+
return m.routeability?.routeable !== false && m.routeability?.status !== "unavailable";
|
|
76
|
+
}
|
|
77
|
+
function familyOf(normId) {
|
|
78
|
+
return normId.replace(/[\d.]+/g, "").replace(/-+/g, "-").replace(/-$/, "");
|
|
79
|
+
}
|
|
80
|
+
function buildCatalog(raw, opts) {
|
|
81
|
+
const candidates = raw.filter(
|
|
82
|
+
(m) => m.id && isRouteable(m) && isChatModel(m) && !EXCLUDED_ID.test(normalizeModelId(m.id))
|
|
83
|
+
);
|
|
84
|
+
const groups = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const m of candidates) {
|
|
86
|
+
const key = `${m._provider ?? ""}::${normalizeModelId(m.id)}`;
|
|
87
|
+
const g = groups.get(key);
|
|
88
|
+
if (g) g.push(m);
|
|
89
|
+
else groups.set(key, [m]);
|
|
90
|
+
}
|
|
91
|
+
const reps = [];
|
|
92
|
+
for (const group of groups.values()) {
|
|
93
|
+
group.sort((a, b) => aliasPenalty(a.id) - aliasPenalty(b.id) || a.id.length - b.id.length);
|
|
94
|
+
const rep = group[0];
|
|
95
|
+
const mergedParams = new Set(group.flatMap((m) => m.supported_parameters ?? []));
|
|
96
|
+
reps.push({ model: rep, normId: normalizeModelId(rep.id), mergedParams });
|
|
97
|
+
}
|
|
98
|
+
const featuredIds = [];
|
|
99
|
+
for (const rule of FEATURED_RULES) {
|
|
100
|
+
const matches = reps.filter(
|
|
101
|
+
(r) => rule.providers.includes(r.model._provider ?? "") && rule.match.test(r.normId) && !featuredIds.includes(r.model.id)
|
|
102
|
+
);
|
|
103
|
+
if (!matches.length) continue;
|
|
104
|
+
matches.sort(
|
|
105
|
+
(a, b) => compareVersions(versionOf(b.normId), versionOf(a.normId)) || Number(a.normId.includes("preview")) - Number(b.normId.includes("preview")) || a.model.id.length - b.model.id.length
|
|
106
|
+
);
|
|
107
|
+
featuredIds.push(matches[0].model.id);
|
|
108
|
+
}
|
|
109
|
+
const toCatalogModel = (r) => {
|
|
110
|
+
const m = r.model;
|
|
111
|
+
const provider = m._provider ?? "unknown";
|
|
112
|
+
return {
|
|
113
|
+
id: m.id,
|
|
114
|
+
name: m.name ?? m.id,
|
|
115
|
+
provider,
|
|
116
|
+
description: m.description ? m.description.slice(0, 160) : void 0,
|
|
117
|
+
contextLength: m.context_length,
|
|
118
|
+
pricing: m.pricing?.prompt || m.pricing?.completion ? { prompt: m.pricing.prompt ?? void 0, completion: m.pricing.completion ?? void 0 } : void 0,
|
|
119
|
+
supportsTools: r.mergedParams.has("tools") || TOOL_CAPABLE_FAMILY.test(r.normId),
|
|
120
|
+
supportsReasoning: r.mergedParams.has("reasoning") || r.mergedParams.has("include_reasoning"),
|
|
121
|
+
featured: featuredIds.includes(m.id)
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
const featured = featuredIds.map((id) => reps.find((r) => r.model.id === id)).map(toCatalogModel);
|
|
125
|
+
const rest = reps.filter((r) => !featuredIds.includes(r.model.id)).sort((a, b) => {
|
|
126
|
+
const pa = providerRank(a.model._provider ?? "");
|
|
127
|
+
const pb = providerRank(b.model._provider ?? "");
|
|
128
|
+
if (pa !== pb) return pa - pb;
|
|
129
|
+
const fa = familyOf(a.normId);
|
|
130
|
+
const fb = familyOf(b.normId);
|
|
131
|
+
if (fa !== fb) return fa.localeCompare(fb);
|
|
132
|
+
return compareVersions(versionOf(b.normId), versionOf(a.normId)) || a.model.id.localeCompare(b.model.id);
|
|
133
|
+
}).map(toCatalogModel);
|
|
134
|
+
const models = [...featured, ...rest];
|
|
135
|
+
const preferred = opts?.preferredDefault;
|
|
136
|
+
const defaultModelId = preferred && models.find((m) => m.id === preferred || normalizeModelId(m.id) === normalizeModelId(preferred))?.id || featured.find((m) => m.supportsTools)?.id || models[0]?.id || null;
|
|
137
|
+
return { defaultModelId, fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), models };
|
|
138
|
+
}
|
|
139
|
+
var CATALOG_TTL_MS = 5 * 60 * 1e3;
|
|
140
|
+
var _cache = null;
|
|
141
|
+
async function fetchModelCatalog(cfg) {
|
|
142
|
+
if (_cache && Date.now() - _cache.at < CATALOG_TTL_MS) {
|
|
143
|
+
return _cache.catalog;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const res = await fetch(`${cfg.baseUrl}/models`, {
|
|
147
|
+
headers: { Authorization: `Bearer ${cfg.apiKey}` }
|
|
148
|
+
});
|
|
149
|
+
if (!res.ok) throw new Error(`Router /models returned ${res.status}`);
|
|
150
|
+
const data = await res.json();
|
|
151
|
+
const catalog = buildCatalog(data.data ?? [], { preferredDefault: cfg.preferredDefault });
|
|
152
|
+
_cache = { catalog, at: Date.now() };
|
|
153
|
+
return catalog;
|
|
154
|
+
} catch (err) {
|
|
155
|
+
if (_cache) return _cache.catalog;
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function __resetCatalogCache() {
|
|
160
|
+
_cache = null;
|
|
161
|
+
}
|
|
6
162
|
|
|
7
163
|
// src/runtime/openai-stream.ts
|
|
8
164
|
async function* toLoopEvents(chunks) {
|
|
9
165
|
const calls = /* @__PURE__ */ new Map();
|
|
10
166
|
for await (const chunk of chunks) {
|
|
167
|
+
if (chunk.usage?.prompt_tokens != null || chunk.usage?.completion_tokens != null) {
|
|
168
|
+
yield {
|
|
169
|
+
type: "usage",
|
|
170
|
+
usage: {
|
|
171
|
+
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
172
|
+
completionTokens: chunk.usage.completion_tokens ?? 0
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
11
176
|
const choice = chunk.choices?.[0];
|
|
12
177
|
if (!choice) continue;
|
|
13
178
|
const content = choice.delta?.content;
|
|
14
179
|
if (content) yield { type: "text", text: content };
|
|
180
|
+
const reasoning = choice.delta?.reasoning_content ?? choice.delta?.thinking;
|
|
181
|
+
if (reasoning) yield { type: "reasoning", text: reasoning };
|
|
15
182
|
for (const tc of choice.delta?.tool_calls ?? []) {
|
|
16
183
|
const cur = calls.get(tc.index) ?? { name: "", args: "" };
|
|
17
184
|
if (tc.id) cur.id = tc.id;
|
|
@@ -42,6 +209,7 @@ function createOpenAICompatStreamTurn(opts) {
|
|
|
42
209
|
model: opts.model,
|
|
43
210
|
messages,
|
|
44
211
|
stream: true,
|
|
212
|
+
stream_options: { include_usage: true },
|
|
45
213
|
...opts.tools && opts.tools.length > 0 ? { tools: opts.tools } : {},
|
|
46
214
|
...opts.temperature != null ? { temperature: opts.temperature } : {},
|
|
47
215
|
...opts.extraBody
|
|
@@ -245,10 +413,14 @@ async function* streamAppToolLoop(opts) {
|
|
|
245
413
|
}
|
|
246
414
|
|
|
247
415
|
export {
|
|
416
|
+
normalizeModelId,
|
|
417
|
+
buildCatalog,
|
|
418
|
+
fetchModelCatalog,
|
|
419
|
+
__resetCatalogCache,
|
|
248
420
|
toLoopEvents,
|
|
249
421
|
createOpenAICompatStreamTurn,
|
|
250
422
|
createAgentRuntime,
|
|
251
423
|
runAppToolLoop,
|
|
252
424
|
streamAppToolLoop
|
|
253
425
|
};
|
|
254
|
-
//# sourceMappingURL=chunk-
|
|
426
|
+
//# sourceMappingURL=chunk-TH2AOJJM.js.map
|