llmist 18.2.0 → 18.4.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-DNB4DPM4.js → chunk-I4KQ7U7F.js} +23 -4
- package/dist/chunk-I4KQ7U7F.js.map +1 -0
- package/dist/index.cjs +2568 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +711 -2
- package/dist/index.d.ts +711 -2
- package/dist/index.js +2390 -84
- package/dist/index.js.map +1 -1
- package/dist/{runtime-KB7US2FQ.js → runtime-IUKW77QX.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-DNB4DPM4.js.map +0 -1
- /package/dist/{runtime-KB7US2FQ.js.map → runtime-IUKW77QX.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
toBase64,
|
|
82
82
|
validateGadgetSchema,
|
|
83
83
|
withErrorHandling
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-I4KQ7U7F.js";
|
|
85
85
|
|
|
86
86
|
// src/core/execution-tree-aggregator.ts
|
|
87
87
|
var ExecutionTreeAggregator;
|
|
@@ -93,6 +93,8 @@ var init_execution_tree_aggregator = __esm({
|
|
|
93
93
|
this.nodes = nodes;
|
|
94
94
|
this.getDescendants = getDescendants;
|
|
95
95
|
}
|
|
96
|
+
nodes;
|
|
97
|
+
getDescendants;
|
|
96
98
|
// ===========================================================================
|
|
97
99
|
// Private helpers
|
|
98
100
|
// ===========================================================================
|
|
@@ -2467,6 +2469,18 @@ async function runWithHandlers(agentGenerator, handlers) {
|
|
|
2467
2469
|
});
|
|
2468
2470
|
}
|
|
2469
2471
|
break;
|
|
2472
|
+
case "gadget_args_partial":
|
|
2473
|
+
if (handlers.onGadgetArgsPartial) {
|
|
2474
|
+
await handlers.onGadgetArgsPartial({
|
|
2475
|
+
gadgetName: event.gadgetName,
|
|
2476
|
+
invocationId: event.invocationId,
|
|
2477
|
+
fieldPath: event.fieldPath,
|
|
2478
|
+
value: event.value,
|
|
2479
|
+
delta: event.delta,
|
|
2480
|
+
isFieldComplete: event.isFieldComplete
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
break;
|
|
2470
2484
|
case "gadget_result":
|
|
2471
2485
|
if (handlers.onGadgetResult) {
|
|
2472
2486
|
await handlers.onGadgetResult(event.result);
|
|
@@ -3624,7 +3638,7 @@ var init_gadget_output_store = __esm({
|
|
|
3624
3638
|
charCount: content.length,
|
|
3625
3639
|
byteSize: encoder.encode(content).length,
|
|
3626
3640
|
lineCount: lines.length,
|
|
3627
|
-
maxLineLength: lines.reduce((
|
|
3641
|
+
maxLineLength: lines.reduce((max2, line) => Math.max(max2, line.length), 0),
|
|
3628
3642
|
timestamp: /* @__PURE__ */ new Date()
|
|
3629
3643
|
};
|
|
3630
3644
|
this.outputs.set(id, stored);
|
|
@@ -6274,6 +6288,7 @@ var init_base_provider = __esm({
|
|
|
6274
6288
|
constructor(client) {
|
|
6275
6289
|
this.client = client;
|
|
6276
6290
|
}
|
|
6291
|
+
client;
|
|
6277
6292
|
/**
|
|
6278
6293
|
* Template method that defines the skeleton of the streaming algorithm.
|
|
6279
6294
|
* This orchestrates the four-step process without dictating provider-specific details.
|
|
@@ -6643,6 +6658,7 @@ var init_gemini_cache_manager = __esm({
|
|
|
6643
6658
|
constructor(client) {
|
|
6644
6659
|
this.client = client;
|
|
6645
6660
|
}
|
|
6661
|
+
client;
|
|
6646
6662
|
activeCache = null;
|
|
6647
6663
|
/**
|
|
6648
6664
|
* Get or create a cache for the given content.
|
|
@@ -7185,6 +7201,621 @@ var init_gemini_models = __esm({
|
|
|
7185
7201
|
}
|
|
7186
7202
|
});
|
|
7187
7203
|
|
|
7204
|
+
// src/research/constants.ts
|
|
7205
|
+
var RESEARCH_DEFAULT_TIMEOUT_MS, GEMINI_RESEARCH_MAX_DURATION_MS, OPENAI_RESEARCH_HTTP_TIMEOUT_MS, OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS, RESEARCH_POLL_INTERVAL_MS, RESEARCH_POLL_MAX_INTERVAL_MS, RESEARCH_POLL_BACKOFF_FACTOR, RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS, RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS, RESEARCH_COST_DECIMALS, TOKENS_PER_MILLION, SEARCHES_PER_THOUSAND, MS_PER_DAY;
|
|
7206
|
+
var init_constants3 = __esm({
|
|
7207
|
+
"src/research/constants.ts"() {
|
|
7208
|
+
"use strict";
|
|
7209
|
+
RESEARCH_DEFAULT_TIMEOUT_MS = 36e5;
|
|
7210
|
+
GEMINI_RESEARCH_MAX_DURATION_MS = 36e5;
|
|
7211
|
+
OPENAI_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
|
|
7212
|
+
OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
|
|
7213
|
+
RESEARCH_POLL_INTERVAL_MS = 1e4;
|
|
7214
|
+
RESEARCH_POLL_MAX_INTERVAL_MS = 6e4;
|
|
7215
|
+
RESEARCH_POLL_BACKOFF_FACTOR = 1.5;
|
|
7216
|
+
RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS = 5;
|
|
7217
|
+
RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS = 30;
|
|
7218
|
+
RESEARCH_COST_DECIMALS = 6;
|
|
7219
|
+
TOKENS_PER_MILLION = 1e6;
|
|
7220
|
+
SEARCHES_PER_THOUSAND = 1e3;
|
|
7221
|
+
MS_PER_DAY = 864e5;
|
|
7222
|
+
}
|
|
7223
|
+
});
|
|
7224
|
+
|
|
7225
|
+
// src/research/errors.ts
|
|
7226
|
+
var ResearchNotSupportedError, ResearchJobNotResumableError, ResearchNotPollableError, ResearchTimeoutError, ResearchDeprecatedModelError, ResearchValidationError, ResearchStreamConsumedError;
|
|
7227
|
+
var init_errors3 = __esm({
|
|
7228
|
+
"src/research/errors.ts"() {
|
|
7229
|
+
"use strict";
|
|
7230
|
+
ResearchNotSupportedError = class extends Error {
|
|
7231
|
+
constructor(message) {
|
|
7232
|
+
super(message);
|
|
7233
|
+
this.name = "ResearchNotSupportedError";
|
|
7234
|
+
}
|
|
7235
|
+
};
|
|
7236
|
+
ResearchJobNotResumableError = class extends Error {
|
|
7237
|
+
constructor(message) {
|
|
7238
|
+
super(message);
|
|
7239
|
+
this.name = "ResearchJobNotResumableError";
|
|
7240
|
+
}
|
|
7241
|
+
};
|
|
7242
|
+
ResearchNotPollableError = class extends Error {
|
|
7243
|
+
constructor(message) {
|
|
7244
|
+
super(message);
|
|
7245
|
+
this.name = "ResearchNotPollableError";
|
|
7246
|
+
}
|
|
7247
|
+
};
|
|
7248
|
+
ResearchTimeoutError = class extends Error {
|
|
7249
|
+
timeoutMs;
|
|
7250
|
+
constructor(timeoutMs) {
|
|
7251
|
+
super(
|
|
7252
|
+
`Research run exceeded the client-side time budget of ${timeoutMs}ms. A background job keeps running server-side \u2014 re-attach via its ref.`
|
|
7253
|
+
);
|
|
7254
|
+
this.name = "ResearchTimeoutError";
|
|
7255
|
+
this.timeoutMs = timeoutMs;
|
|
7256
|
+
}
|
|
7257
|
+
};
|
|
7258
|
+
ResearchDeprecatedModelError = class extends Error {
|
|
7259
|
+
modelId;
|
|
7260
|
+
shutdownDate;
|
|
7261
|
+
replacement;
|
|
7262
|
+
constructor(params) {
|
|
7263
|
+
super(
|
|
7264
|
+
`Research model "${params.modelId}" was shut down by its provider on ${params.shutdownDate}.` + (params.replacement ? ` Use "${params.replacement}" instead.` : "")
|
|
7265
|
+
);
|
|
7266
|
+
this.name = "ResearchDeprecatedModelError";
|
|
7267
|
+
this.modelId = params.modelId;
|
|
7268
|
+
this.shutdownDate = params.shutdownDate;
|
|
7269
|
+
this.replacement = params.replacement;
|
|
7270
|
+
}
|
|
7271
|
+
};
|
|
7272
|
+
ResearchValidationError = class extends Error {
|
|
7273
|
+
constructor(message) {
|
|
7274
|
+
super(message);
|
|
7275
|
+
this.name = "ResearchValidationError";
|
|
7276
|
+
}
|
|
7277
|
+
};
|
|
7278
|
+
ResearchStreamConsumedError = class extends Error {
|
|
7279
|
+
constructor() {
|
|
7280
|
+
super(
|
|
7281
|
+
"This research job's event stream was already consumed. Iterate events() (or the job itself) once; use result() for the aggregated outcome."
|
|
7282
|
+
);
|
|
7283
|
+
this.name = "ResearchStreamConsumedError";
|
|
7284
|
+
}
|
|
7285
|
+
};
|
|
7286
|
+
}
|
|
7287
|
+
});
|
|
7288
|
+
|
|
7289
|
+
// src/providers/gemini-research.ts
|
|
7290
|
+
function buildGeminiResearchRequest(options, agentId) {
|
|
7291
|
+
if (options.background === false) {
|
|
7292
|
+
throw new ResearchValidationError(
|
|
7293
|
+
"Gemini deep research requires background execution \u2014 background: false is not supported."
|
|
7294
|
+
);
|
|
7295
|
+
}
|
|
7296
|
+
return {
|
|
7297
|
+
agent: agentId,
|
|
7298
|
+
input: options.query,
|
|
7299
|
+
background: true,
|
|
7300
|
+
store: true,
|
|
7301
|
+
agent_config: {
|
|
7302
|
+
type: "deep-research",
|
|
7303
|
+
thinking_summaries: options.reasoning?.includeThinking === false ? "none" : "auto"
|
|
7304
|
+
},
|
|
7305
|
+
...options.systemPrompt ? { system_instruction: options.systemPrompt } : {},
|
|
7306
|
+
...options.previousJobId ? { previous_interaction_id: options.previousJobId } : {},
|
|
7307
|
+
...options.extra ?? {}
|
|
7308
|
+
};
|
|
7309
|
+
}
|
|
7310
|
+
function mapInteractionStatus(status) {
|
|
7311
|
+
if (status && GEMINI_STATUS_VALUES.has(status)) {
|
|
7312
|
+
return status;
|
|
7313
|
+
}
|
|
7314
|
+
return "in_progress";
|
|
7315
|
+
}
|
|
7316
|
+
function annotationToCitation(annotation) {
|
|
7317
|
+
if (annotation.type !== "url_citation" || !annotation.url) return void 0;
|
|
7318
|
+
return {
|
|
7319
|
+
url: annotation.url,
|
|
7320
|
+
title: annotation.title,
|
|
7321
|
+
startIndex: annotation.start_index,
|
|
7322
|
+
endIndex: annotation.end_index
|
|
7323
|
+
};
|
|
7324
|
+
}
|
|
7325
|
+
function extractReportFromInteraction(interaction) {
|
|
7326
|
+
const parts = [];
|
|
7327
|
+
const citations = [];
|
|
7328
|
+
for (const step of interaction.steps ?? []) {
|
|
7329
|
+
if (step.type !== "model_output") continue;
|
|
7330
|
+
for (const content of step.content ?? []) {
|
|
7331
|
+
if (content.type !== "text") continue;
|
|
7332
|
+
parts.push(content.text);
|
|
7333
|
+
for (const annotation of content.annotations ?? []) {
|
|
7334
|
+
const citation = annotationToCitation(annotation);
|
|
7335
|
+
if (citation) citations.push(citation);
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
return { report: parts.join(""), citations };
|
|
7340
|
+
}
|
|
7341
|
+
function countSearchesInSteps(interaction) {
|
|
7342
|
+
let searches = 0;
|
|
7343
|
+
for (const step of interaction.steps ?? []) {
|
|
7344
|
+
if (step.type === "google_search_call") {
|
|
7345
|
+
searches += step.arguments?.queries?.length ?? 1;
|
|
7346
|
+
}
|
|
7347
|
+
}
|
|
7348
|
+
return searches;
|
|
7349
|
+
}
|
|
7350
|
+
function usageFromInteraction(interaction, streamedSearches) {
|
|
7351
|
+
const usage = interaction.usage;
|
|
7352
|
+
const searches = streamedSearches ?? countSearchesInSteps(interaction);
|
|
7353
|
+
return {
|
|
7354
|
+
inputTokens: usage?.total_input_tokens ?? 0,
|
|
7355
|
+
outputTokens: usage?.total_output_tokens ?? 0,
|
|
7356
|
+
totalTokens: usage?.total_tokens ?? 0,
|
|
7357
|
+
cachedInputTokens: usage?.total_cached_tokens,
|
|
7358
|
+
reasoningTokens: usage?.total_thought_tokens,
|
|
7359
|
+
...searches > 0 ? { searches } : {}
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
function doneEventFromInteraction(interaction, streamedSearches) {
|
|
7363
|
+
const { report, citations } = extractReportFromInteraction(interaction);
|
|
7364
|
+
return {
|
|
7365
|
+
type: "done",
|
|
7366
|
+
result: {
|
|
7367
|
+
status: mapInteractionStatus(interaction.status),
|
|
7368
|
+
report,
|
|
7369
|
+
citations,
|
|
7370
|
+
usage: usageFromInteraction(interaction, streamedSearches),
|
|
7371
|
+
raw: interaction
|
|
7372
|
+
}
|
|
7373
|
+
};
|
|
7374
|
+
}
|
|
7375
|
+
async function* normalizeInteractionsStream(stream2, fetchFinal) {
|
|
7376
|
+
let streamedSearches = 0;
|
|
7377
|
+
const countedSearchSteps = /* @__PURE__ */ new Set();
|
|
7378
|
+
let interactionId;
|
|
7379
|
+
for await (const event of stream2) {
|
|
7380
|
+
const cursor = event.event_id;
|
|
7381
|
+
switch (event.event_type) {
|
|
7382
|
+
case "interaction.created": {
|
|
7383
|
+
const interaction = event.interaction;
|
|
7384
|
+
interactionId = interaction.id;
|
|
7385
|
+
yield { type: "created", jobId: interaction.id, cursor, rawEvent: event };
|
|
7386
|
+
yield { type: "status", status: mapInteractionStatus(interaction.status), cursor };
|
|
7387
|
+
break;
|
|
7388
|
+
}
|
|
7389
|
+
case "interaction.status_update":
|
|
7390
|
+
yield {
|
|
7391
|
+
type: "status",
|
|
7392
|
+
status: mapInteractionStatus(event.status),
|
|
7393
|
+
cursor,
|
|
7394
|
+
rawEvent: event
|
|
7395
|
+
};
|
|
7396
|
+
break;
|
|
7397
|
+
case "step.start": {
|
|
7398
|
+
const step = event.step;
|
|
7399
|
+
if (step.type === "thought") {
|
|
7400
|
+
yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
|
|
7401
|
+
} else if (step.type === "model_output") {
|
|
7402
|
+
yield { type: "phase", phase: "writing", cursor, rawEvent: event };
|
|
7403
|
+
} else if (step.type === "google_search_call") {
|
|
7404
|
+
if (!countedSearchSteps.has(event.index)) {
|
|
7405
|
+
countedSearchSteps.add(event.index);
|
|
7406
|
+
streamedSearches += step.arguments?.queries?.length ?? 1;
|
|
7407
|
+
}
|
|
7408
|
+
yield { type: "phase", phase: "searching", cursor, rawEvent: event };
|
|
7409
|
+
yield {
|
|
7410
|
+
type: "search",
|
|
7411
|
+
action: "search",
|
|
7412
|
+
status: "started",
|
|
7413
|
+
query: step.arguments?.queries?.join("; "),
|
|
7414
|
+
cursor
|
|
7415
|
+
};
|
|
7416
|
+
} else if (step.type === "google_search_result") {
|
|
7417
|
+
yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
|
|
7418
|
+
}
|
|
7419
|
+
break;
|
|
7420
|
+
}
|
|
7421
|
+
case "step.delta":
|
|
7422
|
+
yield* normalizeDelta(event, cursor, (queries) => {
|
|
7423
|
+
if (!countedSearchSteps.has(event.index)) {
|
|
7424
|
+
countedSearchSteps.add(event.index);
|
|
7425
|
+
streamedSearches += queries;
|
|
7426
|
+
}
|
|
7427
|
+
});
|
|
7428
|
+
break;
|
|
7429
|
+
case "step.stop":
|
|
7430
|
+
break;
|
|
7431
|
+
case "interaction.completed": {
|
|
7432
|
+
let interaction = event.interaction;
|
|
7433
|
+
const finalId = interaction.id ?? interactionId;
|
|
7434
|
+
if (fetchFinal && finalId !== void 0 && !interaction.usage?.total_tokens) {
|
|
7435
|
+
try {
|
|
7436
|
+
interaction = await fetchFinal(finalId);
|
|
7437
|
+
} catch {
|
|
7438
|
+
}
|
|
7439
|
+
}
|
|
7440
|
+
const searches = streamedSearches > 0 ? streamedSearches : void 0;
|
|
7441
|
+
yield {
|
|
7442
|
+
type: "usage",
|
|
7443
|
+
usage: usageFromInteraction(interaction, searches),
|
|
7444
|
+
cursor,
|
|
7445
|
+
rawEvent: event
|
|
7446
|
+
};
|
|
7447
|
+
yield { ...doneEventFromInteraction(interaction, searches), cursor };
|
|
7448
|
+
break;
|
|
7449
|
+
}
|
|
7450
|
+
case "error":
|
|
7451
|
+
if (event.error?.code === "client_closed_request") break;
|
|
7452
|
+
yield {
|
|
7453
|
+
type: "error",
|
|
7454
|
+
error: {
|
|
7455
|
+
message: event.error?.message ?? "Gemini interaction error",
|
|
7456
|
+
code: event.error?.code !== void 0 ? String(event.error.code) : void 0,
|
|
7457
|
+
retryable: isRetryableError(new Error(event.error?.message ?? ""))
|
|
7458
|
+
},
|
|
7459
|
+
cursor,
|
|
7460
|
+
rawEvent: event
|
|
7461
|
+
};
|
|
7462
|
+
break;
|
|
7463
|
+
default: {
|
|
7464
|
+
const type = event.event_type ?? "";
|
|
7465
|
+
if (type.endsWith("error")) {
|
|
7466
|
+
const err = event.error;
|
|
7467
|
+
if (err?.code === "client_closed_request") break;
|
|
7468
|
+
yield {
|
|
7469
|
+
type: "error",
|
|
7470
|
+
error: { message: err?.message ?? "Gemini interaction error", retryable: false },
|
|
7471
|
+
cursor,
|
|
7472
|
+
rawEvent: event
|
|
7473
|
+
};
|
|
7474
|
+
}
|
|
7475
|
+
break;
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
}
|
|
7480
|
+
function* normalizeDelta(event, cursor, onSearchCall) {
|
|
7481
|
+
const delta = event.delta;
|
|
7482
|
+
switch (delta.type) {
|
|
7483
|
+
case "text": {
|
|
7484
|
+
const textDelta = delta;
|
|
7485
|
+
yield { type: "text", delta: textDelta.text, cursor, rawEvent: event };
|
|
7486
|
+
break;
|
|
7487
|
+
}
|
|
7488
|
+
// Citations arrive as dedicated annotation deltas in the current schema.
|
|
7489
|
+
case "text_annotation_delta": {
|
|
7490
|
+
const annotationDelta = delta;
|
|
7491
|
+
for (const annotation of annotationDelta.annotations ?? []) {
|
|
7492
|
+
const citation = annotationToCitation(annotation);
|
|
7493
|
+
if (citation) {
|
|
7494
|
+
yield { type: "citation", citation, cursor, rawEvent: event };
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
break;
|
|
7498
|
+
}
|
|
7499
|
+
// The SDK emits "thought_summary"; some preview docs show "thought" —
|
|
7500
|
+
// handle both defensively.
|
|
7501
|
+
case "thought_summary":
|
|
7502
|
+
case "thought": {
|
|
7503
|
+
const thought = delta;
|
|
7504
|
+
const text3 = thought.content?.type === "text" ? thought.content.text ?? "" : "";
|
|
7505
|
+
if (text3.length > 0) {
|
|
7506
|
+
yield { type: "thinking", delta: text3, cursor, rawEvent: event };
|
|
7507
|
+
}
|
|
7508
|
+
break;
|
|
7509
|
+
}
|
|
7510
|
+
case "thought_signature":
|
|
7511
|
+
break;
|
|
7512
|
+
case "google_search_call": {
|
|
7513
|
+
const call = delta;
|
|
7514
|
+
onSearchCall(call.arguments?.queries?.length ?? 1);
|
|
7515
|
+
yield {
|
|
7516
|
+
type: "search",
|
|
7517
|
+
action: "search",
|
|
7518
|
+
status: "started",
|
|
7519
|
+
query: call.arguments?.queries?.join("; "),
|
|
7520
|
+
cursor,
|
|
7521
|
+
rawEvent: event
|
|
7522
|
+
};
|
|
7523
|
+
break;
|
|
7524
|
+
}
|
|
7525
|
+
case "google_search_result": {
|
|
7526
|
+
const result = delta;
|
|
7527
|
+
yield {
|
|
7528
|
+
type: "search",
|
|
7529
|
+
action: "search",
|
|
7530
|
+
status: "completed",
|
|
7531
|
+
url: result.result?.[0]?.url,
|
|
7532
|
+
cursor,
|
|
7533
|
+
rawEvent: event
|
|
7534
|
+
};
|
|
7535
|
+
break;
|
|
7536
|
+
}
|
|
7537
|
+
case "url_context_call": {
|
|
7538
|
+
const call = delta;
|
|
7539
|
+
yield {
|
|
7540
|
+
type: "search",
|
|
7541
|
+
action: "open_page",
|
|
7542
|
+
status: "started",
|
|
7543
|
+
url: call.arguments?.urls?.[0],
|
|
7544
|
+
cursor,
|
|
7545
|
+
rawEvent: event
|
|
7546
|
+
};
|
|
7547
|
+
break;
|
|
7548
|
+
}
|
|
7549
|
+
case "url_context_result":
|
|
7550
|
+
yield { type: "search", action: "open_page", status: "completed", cursor, rawEvent: event };
|
|
7551
|
+
break;
|
|
7552
|
+
case "code_execution_call":
|
|
7553
|
+
yield { type: "tool", tool: "code_interpreter", status: "started", cursor, rawEvent: event };
|
|
7554
|
+
break;
|
|
7555
|
+
case "code_execution_result":
|
|
7556
|
+
yield {
|
|
7557
|
+
type: "tool",
|
|
7558
|
+
tool: "code_interpreter",
|
|
7559
|
+
status: "completed",
|
|
7560
|
+
cursor,
|
|
7561
|
+
rawEvent: event
|
|
7562
|
+
};
|
|
7563
|
+
break;
|
|
7564
|
+
case "file_search_call":
|
|
7565
|
+
yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
|
|
7566
|
+
break;
|
|
7567
|
+
case "file_search_result":
|
|
7568
|
+
yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
|
|
7569
|
+
break;
|
|
7570
|
+
case "mcp_server_tool_call":
|
|
7571
|
+
yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
|
|
7572
|
+
break;
|
|
7573
|
+
case "mcp_server_tool_result":
|
|
7574
|
+
yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
|
|
7575
|
+
break;
|
|
7576
|
+
default:
|
|
7577
|
+
break;
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
function abortError() {
|
|
7581
|
+
const error = new Error("The operation was aborted");
|
|
7582
|
+
error.name = "AbortError";
|
|
7583
|
+
return error;
|
|
7584
|
+
}
|
|
7585
|
+
function sleep(ms, signal) {
|
|
7586
|
+
return new Promise((resolve2, reject) => {
|
|
7587
|
+
if (signal?.aborted) {
|
|
7588
|
+
reject(abortError());
|
|
7589
|
+
return;
|
|
7590
|
+
}
|
|
7591
|
+
const timer = setTimeout(() => {
|
|
7592
|
+
signal?.removeEventListener("abort", onAbort);
|
|
7593
|
+
resolve2();
|
|
7594
|
+
}, ms);
|
|
7595
|
+
timer.unref?.();
|
|
7596
|
+
const onAbort = () => {
|
|
7597
|
+
clearTimeout(timer);
|
|
7598
|
+
reject(abortError());
|
|
7599
|
+
};
|
|
7600
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
7601
|
+
});
|
|
7602
|
+
}
|
|
7603
|
+
async function withRetries(fn, maxRetries, signal) {
|
|
7604
|
+
let attempt = 0;
|
|
7605
|
+
while (true) {
|
|
7606
|
+
try {
|
|
7607
|
+
return await fn();
|
|
7608
|
+
} catch (error) {
|
|
7609
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
7610
|
+
if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
|
|
7611
|
+
throw err;
|
|
7612
|
+
}
|
|
7613
|
+
attempt += 1;
|
|
7614
|
+
await sleep(extractRetryAfterMs(err) ?? RESEARCH_POLL_INTERVAL_MS, signal);
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
}
|
|
7618
|
+
async function* withTerminalReconciliation(events, client, jobIdHint, signal) {
|
|
7619
|
+
let jobId = jobIdHint;
|
|
7620
|
+
let sawDone = false;
|
|
7621
|
+
for await (const event of events) {
|
|
7622
|
+
if (event.type === "created" && event.jobId !== null) jobId = event.jobId;
|
|
7623
|
+
if (event.type === "done") sawDone = true;
|
|
7624
|
+
yield event;
|
|
7625
|
+
}
|
|
7626
|
+
if (sawDone || jobId === void 0) return;
|
|
7627
|
+
const fetchInteraction = finalInteractionFetcher(client, signal);
|
|
7628
|
+
while (true) {
|
|
7629
|
+
const interaction = await fetchInteraction(jobId);
|
|
7630
|
+
const status = mapInteractionStatus(interaction.status);
|
|
7631
|
+
yield { type: "status", status };
|
|
7632
|
+
if (isTerminalStatus(status)) {
|
|
7633
|
+
yield { type: "usage", usage: usageFromInteraction(interaction) };
|
|
7634
|
+
yield doneEventFromInteraction(interaction);
|
|
7635
|
+
return;
|
|
7636
|
+
}
|
|
7637
|
+
await sleep(RESEARCH_POLL_INTERVAL_MS, signal);
|
|
7638
|
+
}
|
|
7639
|
+
}
|
|
7640
|
+
function startGeminiResearch(client, options, agentId, _spec) {
|
|
7641
|
+
const request = buildGeminiResearchRequest(options, agentId);
|
|
7642
|
+
return (async function* () {
|
|
7643
|
+
const interaction = await withRetries(
|
|
7644
|
+
() => client.interactions.create(request, requestOptions(options.signal)),
|
|
7645
|
+
GEMINI_RESEARCH_CREATE_MAX_RETRIES,
|
|
7646
|
+
options.signal
|
|
7647
|
+
);
|
|
7648
|
+
if (interaction.id === void 0) {
|
|
7649
|
+
throw new Error(
|
|
7650
|
+
"Gemini interactions.create returned no interaction id \u2014 cannot stream events."
|
|
7651
|
+
);
|
|
7652
|
+
}
|
|
7653
|
+
yield { type: "created", jobId: interaction.id, rawEvent: interaction };
|
|
7654
|
+
yield { type: "status", status: mapInteractionStatus(interaction.status) };
|
|
7655
|
+
const stream2 = await client.interactions.get(
|
|
7656
|
+
interaction.id,
|
|
7657
|
+
{ stream: true },
|
|
7658
|
+
requestOptions(options.signal)
|
|
7659
|
+
);
|
|
7660
|
+
for await (const event of withTerminalReconciliation(
|
|
7661
|
+
normalizeInteractionsStream(stream2, finalInteractionFetcher(client, options.signal)),
|
|
7662
|
+
client,
|
|
7663
|
+
interaction.id,
|
|
7664
|
+
options.signal
|
|
7665
|
+
)) {
|
|
7666
|
+
if (event.type === "created") continue;
|
|
7667
|
+
yield event;
|
|
7668
|
+
}
|
|
7669
|
+
})();
|
|
7670
|
+
}
|
|
7671
|
+
function isTerminalStatus(status) {
|
|
7672
|
+
return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete" || status === "budget_exceeded";
|
|
7673
|
+
}
|
|
7674
|
+
function resumeGeminiResearch(client, ref, signal) {
|
|
7675
|
+
return (async function* () {
|
|
7676
|
+
const current = await client.interactions.get(
|
|
7677
|
+
ref.jobId,
|
|
7678
|
+
void 0,
|
|
7679
|
+
requestOptions(signal)
|
|
7680
|
+
);
|
|
7681
|
+
const status = mapInteractionStatus(current.status);
|
|
7682
|
+
if (isTerminalStatus(status)) {
|
|
7683
|
+
yield { type: "status", status };
|
|
7684
|
+
const { report, citations } = extractReportFromInteraction(current);
|
|
7685
|
+
if (report.length > 0) {
|
|
7686
|
+
yield { type: "text", delta: report };
|
|
7687
|
+
}
|
|
7688
|
+
for (const citation of citations) {
|
|
7689
|
+
yield { type: "citation", citation };
|
|
7690
|
+
}
|
|
7691
|
+
yield { type: "usage", usage: usageFromInteraction(current) };
|
|
7692
|
+
yield doneEventFromInteraction(current);
|
|
7693
|
+
return;
|
|
7694
|
+
}
|
|
7695
|
+
const stream2 = await client.interactions.get(
|
|
7696
|
+
ref.jobId,
|
|
7697
|
+
{ stream: true, ...ref.cursor !== void 0 ? { last_event_id: ref.cursor } : {} },
|
|
7698
|
+
requestOptions(signal)
|
|
7699
|
+
);
|
|
7700
|
+
yield* withTerminalReconciliation(
|
|
7701
|
+
normalizeInteractionsStream(stream2, finalInteractionFetcher(client, signal)),
|
|
7702
|
+
client,
|
|
7703
|
+
ref.jobId,
|
|
7704
|
+
signal
|
|
7705
|
+
);
|
|
7706
|
+
})();
|
|
7707
|
+
}
|
|
7708
|
+
async function getGeminiResearchStatus(client, ref) {
|
|
7709
|
+
const interaction = await client.interactions.get(
|
|
7710
|
+
ref.jobId
|
|
7711
|
+
);
|
|
7712
|
+
const status = mapInteractionStatus(interaction.status);
|
|
7713
|
+
if (!isTerminalStatus(status)) {
|
|
7714
|
+
return { status };
|
|
7715
|
+
}
|
|
7716
|
+
const { report, citations } = extractReportFromInteraction(interaction);
|
|
7717
|
+
return {
|
|
7718
|
+
status,
|
|
7719
|
+
result: {
|
|
7720
|
+
jobId: interaction.id,
|
|
7721
|
+
provider: "gemini",
|
|
7722
|
+
model: ref.model,
|
|
7723
|
+
status,
|
|
7724
|
+
report,
|
|
7725
|
+
citations,
|
|
7726
|
+
usage: usageFromInteraction(interaction),
|
|
7727
|
+
raw: interaction
|
|
7728
|
+
}
|
|
7729
|
+
};
|
|
7730
|
+
}
|
|
7731
|
+
async function cancelGeminiResearch(client, ref) {
|
|
7732
|
+
await client.interactions.cancel(ref.jobId);
|
|
7733
|
+
}
|
|
7734
|
+
var GEMINI_RESEARCH_CREATE_MAX_RETRIES, GEMINI_STATUS_VALUES, requestOptions, finalInteractionFetcher;
|
|
7735
|
+
var init_gemini_research = __esm({
|
|
7736
|
+
"src/providers/gemini-research.ts"() {
|
|
7737
|
+
"use strict";
|
|
7738
|
+
init_errors2();
|
|
7739
|
+
init_retry();
|
|
7740
|
+
init_constants3();
|
|
7741
|
+
init_errors3();
|
|
7742
|
+
GEMINI_RESEARCH_CREATE_MAX_RETRIES = 3;
|
|
7743
|
+
GEMINI_STATUS_VALUES = /* @__PURE__ */ new Set([
|
|
7744
|
+
"queued",
|
|
7745
|
+
"in_progress",
|
|
7746
|
+
"requires_action",
|
|
7747
|
+
"completed",
|
|
7748
|
+
"failed",
|
|
7749
|
+
"cancelled",
|
|
7750
|
+
"incomplete",
|
|
7751
|
+
"budget_exceeded"
|
|
7752
|
+
]);
|
|
7753
|
+
requestOptions = (signal) => ({
|
|
7754
|
+
timeout: GEMINI_RESEARCH_MAX_DURATION_MS,
|
|
7755
|
+
signal,
|
|
7756
|
+
// The SDK's own retries would fight llmist's retry policy.
|
|
7757
|
+
maxRetries: 0
|
|
7758
|
+
});
|
|
7759
|
+
finalInteractionFetcher = (client, signal) => (id) => client.interactions.get(
|
|
7760
|
+
id,
|
|
7761
|
+
void 0,
|
|
7762
|
+
requestOptions(signal)
|
|
7763
|
+
);
|
|
7764
|
+
}
|
|
7765
|
+
});
|
|
7766
|
+
|
|
7767
|
+
// src/providers/gemini-research-models.ts
|
|
7768
|
+
function geminiResearchAgent(params) {
|
|
7769
|
+
return {
|
|
7770
|
+
provider: "gemini",
|
|
7771
|
+
modelId: params.agentId,
|
|
7772
|
+
kind: "agent",
|
|
7773
|
+
displayName: params.displayName,
|
|
7774
|
+
// Gemini 3.1 Pro base-tier token rates; >200K-context tier is higher.
|
|
7775
|
+
pricing: { input: 2, output: 12, perThousandSearches: 14 },
|
|
7776
|
+
capabilities: {
|
|
7777
|
+
streaming: true,
|
|
7778
|
+
// The Interactions API requires background execution for deep research.
|
|
7779
|
+
background: true,
|
|
7780
|
+
resumable: true,
|
|
7781
|
+
followUps: true,
|
|
7782
|
+
// Research tools are agent-managed — the tools option is not accepted.
|
|
7783
|
+
tools: []
|
|
7784
|
+
},
|
|
7785
|
+
// Interactions enforces a 60-minute research cap server-side.
|
|
7786
|
+
maxDurationMs: GEMINI_RESEARCH_MAX_DURATION_MS,
|
|
7787
|
+
metadata: params.notes ? { notes: params.notes } : void 0
|
|
7788
|
+
};
|
|
7789
|
+
}
|
|
7790
|
+
function isGeminiResearchModel(agentId) {
|
|
7791
|
+
return byId.has(agentId);
|
|
7792
|
+
}
|
|
7793
|
+
var geminiResearchModels, byId;
|
|
7794
|
+
var init_gemini_research_models = __esm({
|
|
7795
|
+
"src/providers/gemini-research-models.ts"() {
|
|
7796
|
+
"use strict";
|
|
7797
|
+
init_constants3();
|
|
7798
|
+
geminiResearchModels = [
|
|
7799
|
+
geminiResearchAgent({
|
|
7800
|
+
agentId: "deep-research-preview-04-2026",
|
|
7801
|
+
displayName: "Gemini Deep Research (preview 04-2026)",
|
|
7802
|
+
notes: "Speed-optimized deep research agent on a Gemini 3.1 Pro core."
|
|
7803
|
+
}),
|
|
7804
|
+
geminiResearchAgent({
|
|
7805
|
+
agentId: "deep-research-max-preview-04-2026",
|
|
7806
|
+
displayName: "Gemini Deep Research Max (preview 04-2026)",
|
|
7807
|
+
notes: "Maximum-comprehensiveness variant; roughly 2x the searches and tokens per run."
|
|
7808
|
+
}),
|
|
7809
|
+
geminiResearchAgent({
|
|
7810
|
+
agentId: "deep-research-pro-preview-12-2025",
|
|
7811
|
+
displayName: "Gemini Deep Research Pro (preview 12-2025)",
|
|
7812
|
+
notes: "Original deep research agent (Gemini 3 Pro core); prefer the 04-2026 agents."
|
|
7813
|
+
})
|
|
7814
|
+
];
|
|
7815
|
+
byId = new Map(geminiResearchModels.map((spec) => [spec.modelId, spec]));
|
|
7816
|
+
}
|
|
7817
|
+
});
|
|
7818
|
+
|
|
7188
7819
|
// src/providers/gemini-speech-models.ts
|
|
7189
7820
|
function getGeminiSpeechModelSpec(modelId) {
|
|
7190
7821
|
return geminiSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -7382,6 +8013,8 @@ var init_gemini = __esm({
|
|
|
7382
8013
|
init_gemini_cache_manager();
|
|
7383
8014
|
init_gemini_image_models();
|
|
7384
8015
|
init_gemini_models();
|
|
8016
|
+
init_gemini_research();
|
|
8017
|
+
init_gemini_research_models();
|
|
7385
8018
|
init_gemini_speech_models();
|
|
7386
8019
|
init_utils();
|
|
7387
8020
|
GEMINI3_PRO_THINKING_LEVEL = {
|
|
@@ -7601,6 +8234,27 @@ var init_gemini = __esm({
|
|
|
7601
8234
|
format: spec?.defaultFormat ?? "wav"
|
|
7602
8235
|
};
|
|
7603
8236
|
}
|
|
8237
|
+
// =========================================================================
|
|
8238
|
+
// Deep Research (Interactions API)
|
|
8239
|
+
// =========================================================================
|
|
8240
|
+
getResearchModelSpecs() {
|
|
8241
|
+
return geminiResearchModels;
|
|
8242
|
+
}
|
|
8243
|
+
supportsResearch(agentId) {
|
|
8244
|
+
return isGeminiResearchModel(agentId);
|
|
8245
|
+
}
|
|
8246
|
+
startResearch(options, descriptor, spec) {
|
|
8247
|
+
return startGeminiResearch(this.client, options, descriptor.name, spec);
|
|
8248
|
+
}
|
|
8249
|
+
resumeResearch(ref, signal) {
|
|
8250
|
+
return resumeGeminiResearch(this.client, ref, signal);
|
|
8251
|
+
}
|
|
8252
|
+
getResearchStatus(ref) {
|
|
8253
|
+
return getGeminiResearchStatus(this.client, ref);
|
|
8254
|
+
}
|
|
8255
|
+
cancelResearch(ref) {
|
|
8256
|
+
return cancelGeminiResearch(this.client, ref);
|
|
8257
|
+
}
|
|
7604
8258
|
buildApiRequest(options, descriptor, _spec, messages) {
|
|
7605
8259
|
const contents = this.convertMessagesToContents(messages);
|
|
7606
8260
|
return this.buildApiRequestFromContents(options, descriptor, _spec, contents, null, 0);
|
|
@@ -8827,17 +9481,17 @@ var init_openai_compatible_provider = __esm({
|
|
|
8827
9481
|
async executeStreamRequest(payload, signal) {
|
|
8828
9482
|
const client = this.client;
|
|
8829
9483
|
const headers = this.getCustomHeaders();
|
|
8830
|
-
const
|
|
9484
|
+
const requestOptions3 = {};
|
|
8831
9485
|
if (signal) {
|
|
8832
|
-
|
|
9486
|
+
requestOptions3.signal = signal;
|
|
8833
9487
|
}
|
|
8834
9488
|
if (Object.keys(headers).length > 0) {
|
|
8835
|
-
|
|
9489
|
+
requestOptions3.headers = headers;
|
|
8836
9490
|
}
|
|
8837
9491
|
try {
|
|
8838
9492
|
const stream2 = await client.chat.completions.create(
|
|
8839
9493
|
payload,
|
|
8840
|
-
Object.keys(
|
|
9494
|
+
Object.keys(requestOptions3).length > 0 ? requestOptions3 : void 0
|
|
8841
9495
|
);
|
|
8842
9496
|
return stream2;
|
|
8843
9497
|
} catch (error) {
|
|
@@ -9714,73 +10368,616 @@ var init_openai_models = __esm({
|
|
|
9714
10368
|
}
|
|
9715
10369
|
});
|
|
9716
10370
|
|
|
9717
|
-
// src/providers/openai-
|
|
9718
|
-
function
|
|
9719
|
-
|
|
10371
|
+
// src/providers/openai-research.ts
|
|
10372
|
+
function buildTools(options) {
|
|
10373
|
+
const tools = [];
|
|
10374
|
+
for (const tool of options.tools ?? []) {
|
|
10375
|
+
switch (tool.type) {
|
|
10376
|
+
case "web_search":
|
|
10377
|
+
tools.push({ type: "web_search_preview" });
|
|
10378
|
+
break;
|
|
10379
|
+
case "file_search":
|
|
10380
|
+
if (tool.vectorStoreIds.length > OPENAI_FILE_SEARCH_MAX_VECTOR_STORES) {
|
|
10381
|
+
throw new ResearchValidationError(
|
|
10382
|
+
`OpenAI file_search accepts at most ${OPENAI_FILE_SEARCH_MAX_VECTOR_STORES} vector stores (got ${tool.vectorStoreIds.length}).`
|
|
10383
|
+
);
|
|
10384
|
+
}
|
|
10385
|
+
tools.push({ type: "file_search", vector_store_ids: tool.vectorStoreIds });
|
|
10386
|
+
break;
|
|
10387
|
+
case "mcp":
|
|
10388
|
+
tools.push({
|
|
10389
|
+
type: "mcp",
|
|
10390
|
+
server_label: tool.serverLabel,
|
|
10391
|
+
server_url: tool.serverUrl,
|
|
10392
|
+
require_approval: tool.requireApproval ?? "never"
|
|
10393
|
+
});
|
|
10394
|
+
break;
|
|
10395
|
+
case "code_interpreter":
|
|
10396
|
+
tools.push({ type: "code_interpreter", container: { type: "auto" } });
|
|
10397
|
+
break;
|
|
10398
|
+
}
|
|
10399
|
+
}
|
|
10400
|
+
return tools;
|
|
9720
10401
|
}
|
|
9721
|
-
function
|
|
9722
|
-
|
|
10402
|
+
function buildResponsesResearchRequest(options, spec, modelId) {
|
|
10403
|
+
const tools = buildTools(options);
|
|
10404
|
+
const hasDataSource = (options.tools ?? []).some(
|
|
10405
|
+
(tool) => ["web_search", "file_search", "mcp"].includes(tool.type)
|
|
10406
|
+
);
|
|
10407
|
+
if (!hasDataSource) {
|
|
10408
|
+
throw new ResearchValidationError(
|
|
10409
|
+
"OpenAI deep research requires at least one data-source tool (web_search, file_search, or mcp)."
|
|
10410
|
+
);
|
|
10411
|
+
}
|
|
10412
|
+
const background = options.background ?? spec?.capabilities.background ?? true;
|
|
10413
|
+
const input = options.systemPrompt ? `${options.systemPrompt}
|
|
10414
|
+
|
|
10415
|
+
${options.query}` : options.query;
|
|
10416
|
+
const request = {
|
|
10417
|
+
model: modelId,
|
|
10418
|
+
input,
|
|
10419
|
+
background,
|
|
10420
|
+
// Background mode requires stored responses; polling/resume need them too.
|
|
10421
|
+
...background ? { store: true } : {},
|
|
10422
|
+
reasoning: {
|
|
10423
|
+
summary: "auto",
|
|
10424
|
+
...options.reasoning?.effort && options.reasoning.effort !== "none" ? { effort: mapEffort(options.reasoning.effort) } : {}
|
|
10425
|
+
},
|
|
10426
|
+
tools,
|
|
10427
|
+
...options.maxToolCalls !== void 0 ? { max_tool_calls: options.maxToolCalls } : {},
|
|
10428
|
+
...options.extra ?? {}
|
|
10429
|
+
};
|
|
10430
|
+
return request;
|
|
9723
10431
|
}
|
|
9724
|
-
function
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
10432
|
+
function mapEffort(effort) {
|
|
10433
|
+
switch (effort) {
|
|
10434
|
+
case "maximum":
|
|
10435
|
+
return "xhigh";
|
|
10436
|
+
case "low":
|
|
10437
|
+
case "medium":
|
|
10438
|
+
case "high":
|
|
10439
|
+
return effort;
|
|
10440
|
+
default:
|
|
10441
|
+
return "medium";
|
|
9729
10442
|
}
|
|
9730
|
-
|
|
9731
|
-
|
|
10443
|
+
}
|
|
10444
|
+
function extractReportFromResponse(response) {
|
|
10445
|
+
const citations = [];
|
|
10446
|
+
const parts = [];
|
|
10447
|
+
for (const item of response.output ?? []) {
|
|
10448
|
+
if (item.type !== "message") continue;
|
|
10449
|
+
for (const content of item.content ?? []) {
|
|
10450
|
+
if (content.type !== "output_text") continue;
|
|
10451
|
+
parts.push(content.text);
|
|
10452
|
+
for (const annotation of content.annotations ?? []) {
|
|
10453
|
+
const citation = annotationToCitation2(annotation);
|
|
10454
|
+
if (citation) citations.push(citation);
|
|
10455
|
+
}
|
|
10456
|
+
}
|
|
9732
10457
|
}
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
10458
|
+
return { report: parts.join(""), citations };
|
|
10459
|
+
}
|
|
10460
|
+
function annotationToCitation2(annotation) {
|
|
10461
|
+
if (typeof annotation !== "object" || annotation === null) return void 0;
|
|
10462
|
+
const a = annotation;
|
|
10463
|
+
if (a.type !== "url_citation" || typeof a.url !== "string") return void 0;
|
|
10464
|
+
return {
|
|
10465
|
+
url: a.url,
|
|
10466
|
+
title: typeof a.title === "string" ? a.title : void 0,
|
|
10467
|
+
startIndex: typeof a.start_index === "number" ? a.start_index : void 0,
|
|
10468
|
+
endIndex: typeof a.end_index === "number" ? a.end_index : void 0
|
|
10469
|
+
};
|
|
10470
|
+
}
|
|
10471
|
+
function countSearches(response) {
|
|
10472
|
+
let searches = 0;
|
|
10473
|
+
for (const item of response.output ?? []) {
|
|
10474
|
+
if (item.type === "web_search_call") searches += 1;
|
|
9736
10475
|
}
|
|
9737
|
-
return
|
|
10476
|
+
return searches;
|
|
9738
10477
|
}
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
10478
|
+
function usageFromResponse(response) {
|
|
10479
|
+
const usage = response.usage;
|
|
10480
|
+
return {
|
|
10481
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
10482
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
10483
|
+
totalTokens: usage?.total_tokens ?? 0,
|
|
10484
|
+
cachedInputTokens: usage?.input_tokens_details?.cached_tokens,
|
|
10485
|
+
reasoningTokens: usage?.output_tokens_details?.reasoning_tokens,
|
|
10486
|
+
searches: countSearches(response)
|
|
10487
|
+
};
|
|
10488
|
+
}
|
|
10489
|
+
function mapResponseStatus(status) {
|
|
10490
|
+
return status && STATUS_MAP[status] || "in_progress";
|
|
10491
|
+
}
|
|
10492
|
+
function doneEventFromResponse(response) {
|
|
10493
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
10494
|
+
return {
|
|
10495
|
+
type: "done",
|
|
10496
|
+
result: {
|
|
10497
|
+
status: mapResponseStatus(response.status),
|
|
10498
|
+
report,
|
|
10499
|
+
citations,
|
|
10500
|
+
usage: usageFromResponse(response),
|
|
10501
|
+
raw: response
|
|
10502
|
+
}
|
|
10503
|
+
};
|
|
10504
|
+
}
|
|
10505
|
+
async function* normalizeResponsesStream(stream2) {
|
|
10506
|
+
const searchStarted = /* @__PURE__ */ new Set();
|
|
10507
|
+
for await (const event of stream2) {
|
|
10508
|
+
const cursor = String(event.sequence_number);
|
|
10509
|
+
switch (event.type) {
|
|
10510
|
+
case "response.created":
|
|
10511
|
+
yield { type: "created", jobId: event.response.id, cursor, rawEvent: event };
|
|
10512
|
+
yield { type: "status", status: mapResponseStatus(event.response.status), cursor };
|
|
10513
|
+
break;
|
|
10514
|
+
case "response.queued":
|
|
10515
|
+
yield { type: "status", status: "queued", cursor, rawEvent: event };
|
|
10516
|
+
break;
|
|
10517
|
+
case "response.in_progress":
|
|
10518
|
+
yield { type: "status", status: "in_progress", cursor, rawEvent: event };
|
|
10519
|
+
break;
|
|
10520
|
+
case "response.output_item.added": {
|
|
10521
|
+
const item = event.item;
|
|
10522
|
+
if (item.type === "reasoning") {
|
|
10523
|
+
yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
|
|
10524
|
+
} else if (item.type === "web_search_call") {
|
|
10525
|
+
const action = "action" in item ? item.action : void 0;
|
|
10526
|
+
searchStarted.add(item.id);
|
|
10527
|
+
yield {
|
|
10528
|
+
type: "search",
|
|
10529
|
+
action: action?.type ?? "search",
|
|
10530
|
+
status: "started",
|
|
10531
|
+
query: action && "query" in action ? action.query ?? void 0 : void 0,
|
|
10532
|
+
url: action && "url" in action ? action.url ?? void 0 : void 0,
|
|
10533
|
+
cursor,
|
|
10534
|
+
rawEvent: event
|
|
10535
|
+
};
|
|
10536
|
+
} else if (item.type === "code_interpreter_call") {
|
|
10537
|
+
yield {
|
|
10538
|
+
type: "tool",
|
|
10539
|
+
tool: "code_interpreter",
|
|
10540
|
+
status: "started",
|
|
10541
|
+
cursor,
|
|
10542
|
+
rawEvent: event
|
|
10543
|
+
};
|
|
10544
|
+
} else if (item.type === "file_search_call") {
|
|
10545
|
+
yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
|
|
10546
|
+
} else if (item.type === "mcp_call") {
|
|
10547
|
+
yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
|
|
10548
|
+
} else if (item.type === "message") {
|
|
10549
|
+
yield { type: "phase", phase: "writing", cursor, rawEvent: event };
|
|
10550
|
+
}
|
|
10551
|
+
break;
|
|
10552
|
+
}
|
|
10553
|
+
case "response.web_search_call.in_progress":
|
|
10554
|
+
case "response.web_search_call.searching":
|
|
10555
|
+
if (!searchStarted.has(event.item_id)) {
|
|
10556
|
+
searchStarted.add(event.item_id);
|
|
10557
|
+
yield { type: "search", action: "search", status: "started", cursor, rawEvent: event };
|
|
10558
|
+
}
|
|
10559
|
+
break;
|
|
10560
|
+
case "response.web_search_call.completed":
|
|
10561
|
+
yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
|
|
10562
|
+
break;
|
|
10563
|
+
case "response.code_interpreter_call.completed":
|
|
10564
|
+
yield {
|
|
10565
|
+
type: "tool",
|
|
10566
|
+
tool: "code_interpreter",
|
|
10567
|
+
status: "completed",
|
|
10568
|
+
cursor,
|
|
10569
|
+
rawEvent: event
|
|
10570
|
+
};
|
|
10571
|
+
break;
|
|
10572
|
+
case "response.file_search_call.completed":
|
|
10573
|
+
yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
|
|
10574
|
+
break;
|
|
10575
|
+
case "response.mcp_call.completed":
|
|
10576
|
+
yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
|
|
10577
|
+
break;
|
|
10578
|
+
case "response.reasoning_summary_text.delta":
|
|
10579
|
+
yield { type: "thinking", delta: event.delta, cursor, rawEvent: event };
|
|
10580
|
+
break;
|
|
10581
|
+
case "response.output_text.delta":
|
|
10582
|
+
yield { type: "text", delta: event.delta, cursor, rawEvent: event };
|
|
10583
|
+
break;
|
|
10584
|
+
case "response.output_text.annotation.added": {
|
|
10585
|
+
const citation = annotationToCitation2(event.annotation);
|
|
10586
|
+
if (citation) {
|
|
10587
|
+
yield { type: "citation", citation, cursor, rawEvent: event };
|
|
10588
|
+
}
|
|
10589
|
+
break;
|
|
10590
|
+
}
|
|
10591
|
+
case "response.completed":
|
|
10592
|
+
case "response.failed":
|
|
10593
|
+
case "response.incomplete": {
|
|
10594
|
+
const response = event.response;
|
|
10595
|
+
yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
|
|
10596
|
+
if (event.type === "response.failed") {
|
|
10597
|
+
const message = response.error?.message ?? "OpenAI research run failed";
|
|
10598
|
+
yield {
|
|
10599
|
+
type: "error",
|
|
10600
|
+
error: { message, code: response.error?.code ?? void 0, retryable: false },
|
|
10601
|
+
cursor
|
|
10602
|
+
};
|
|
10603
|
+
}
|
|
10604
|
+
yield { ...doneEventFromResponse(response), cursor };
|
|
10605
|
+
break;
|
|
10606
|
+
}
|
|
10607
|
+
case "error": {
|
|
10608
|
+
const err = new Error(event.message ?? "OpenAI stream error");
|
|
10609
|
+
yield {
|
|
10610
|
+
type: "error",
|
|
10611
|
+
error: {
|
|
10612
|
+
message: event.message ?? "OpenAI stream error",
|
|
10613
|
+
code: event.code ?? void 0,
|
|
10614
|
+
retryable: isRetryableError(err)
|
|
10615
|
+
},
|
|
10616
|
+
cursor,
|
|
10617
|
+
rawEvent: event
|
|
10618
|
+
};
|
|
10619
|
+
break;
|
|
10620
|
+
}
|
|
10621
|
+
default: {
|
|
10622
|
+
const type = event.type;
|
|
10623
|
+
if (type === "response.cancelled") {
|
|
10624
|
+
const response = event.response;
|
|
10625
|
+
yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
|
|
10626
|
+
yield { ...doneEventFromResponse(response), cursor };
|
|
10627
|
+
}
|
|
10628
|
+
break;
|
|
10629
|
+
}
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
function sleep2(ms, signal) {
|
|
10634
|
+
return new Promise((resolve2, reject) => {
|
|
10635
|
+
if (signal?.aborted) {
|
|
10636
|
+
reject(abortError2());
|
|
10637
|
+
return;
|
|
10638
|
+
}
|
|
10639
|
+
const timer = setTimeout(() => {
|
|
10640
|
+
signal?.removeEventListener("abort", onAbort);
|
|
10641
|
+
resolve2();
|
|
10642
|
+
}, ms);
|
|
10643
|
+
timer.unref?.();
|
|
10644
|
+
const onAbort = () => {
|
|
10645
|
+
clearTimeout(timer);
|
|
10646
|
+
reject(abortError2());
|
|
10647
|
+
};
|
|
10648
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
10649
|
+
});
|
|
10650
|
+
}
|
|
10651
|
+
function abortError2() {
|
|
10652
|
+
const error = new Error("The operation was aborted");
|
|
10653
|
+
error.name = "AbortError";
|
|
10654
|
+
return error;
|
|
10655
|
+
}
|
|
10656
|
+
async function withRetries2(fn, maxRetries, signal) {
|
|
10657
|
+
let attempt = 0;
|
|
10658
|
+
while (true) {
|
|
10659
|
+
try {
|
|
10660
|
+
return await fn();
|
|
10661
|
+
} catch (error) {
|
|
10662
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
10663
|
+
if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
|
|
10664
|
+
throw err;
|
|
10665
|
+
}
|
|
10666
|
+
attempt += 1;
|
|
10667
|
+
const retryAfter = extractRetryAfterMs(err);
|
|
10668
|
+
await sleep2(retryAfter ?? RESEARCH_POLL_INTERVAL_MS, signal);
|
|
10669
|
+
}
|
|
10670
|
+
}
|
|
10671
|
+
}
|
|
10672
|
+
function startOpenAIResearch(client, options, modelId, spec) {
|
|
10673
|
+
const request = buildResponsesResearchRequest(options, spec, modelId);
|
|
10674
|
+
const streaming = spec?.capabilities.streaming ?? true;
|
|
10675
|
+
if (streaming) {
|
|
10676
|
+
return (async function* () {
|
|
10677
|
+
const stream2 = await withRetries2(
|
|
10678
|
+
() => client.responses.create(
|
|
10679
|
+
{ ...request, stream: true },
|
|
10680
|
+
requestOptions2(options.signal)
|
|
10681
|
+
),
|
|
10682
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES,
|
|
10683
|
+
options.signal
|
|
10684
|
+
);
|
|
10685
|
+
yield* normalizeResponsesStream(stream2);
|
|
10686
|
+
})();
|
|
10687
|
+
}
|
|
10688
|
+
return (async function* () {
|
|
10689
|
+
const created = await withRetries2(
|
|
10690
|
+
() => client.responses.create(
|
|
10691
|
+
{ ...request, background: true, store: true, stream: false },
|
|
10692
|
+
requestOptions2(options.signal)
|
|
10693
|
+
),
|
|
10694
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES,
|
|
10695
|
+
options.signal
|
|
10696
|
+
);
|
|
10697
|
+
yield { type: "created", jobId: created.id, rawEvent: created };
|
|
10698
|
+
yield* pollResponseToCompletion(client, created.id, created, options.signal);
|
|
10699
|
+
})();
|
|
10700
|
+
}
|
|
10701
|
+
function* emitTerminalResponse(response) {
|
|
10702
|
+
const status = mapResponseStatus(response.status);
|
|
10703
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
10704
|
+
if (report.length > 0) {
|
|
10705
|
+
yield { type: "text", delta: report };
|
|
10706
|
+
}
|
|
10707
|
+
for (const citation of citations) {
|
|
10708
|
+
yield { type: "citation", citation };
|
|
10709
|
+
}
|
|
10710
|
+
yield { type: "usage", usage: usageFromResponse(response) };
|
|
10711
|
+
if (status === "failed") {
|
|
10712
|
+
yield {
|
|
10713
|
+
type: "error",
|
|
10714
|
+
error: {
|
|
10715
|
+
message: response.error?.message ?? "OpenAI research run failed",
|
|
10716
|
+
code: response.error?.code ?? void 0,
|
|
10717
|
+
retryable: false
|
|
10718
|
+
}
|
|
10719
|
+
};
|
|
10720
|
+
}
|
|
10721
|
+
yield doneEventFromResponse(response);
|
|
10722
|
+
}
|
|
10723
|
+
async function* pollResponseToCompletion(client, responseId, initial, signal) {
|
|
10724
|
+
let interval = RESEARCH_POLL_INTERVAL_MS;
|
|
10725
|
+
let current = initial;
|
|
10726
|
+
while (true) {
|
|
10727
|
+
const status = mapResponseStatus(current?.status);
|
|
10728
|
+
yield { type: "status", status, rawEvent: current };
|
|
10729
|
+
if (current && isTerminalStatus2(status)) {
|
|
10730
|
+
yield* emitTerminalResponse(current);
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10733
|
+
await sleep2(interval, signal);
|
|
10734
|
+
interval = Math.min(interval * RESEARCH_POLL_BACKOFF_FACTOR, RESEARCH_POLL_MAX_INTERVAL_MS);
|
|
10735
|
+
current = await withRetries2(
|
|
10736
|
+
() => client.responses.retrieve(responseId, {}, requestOptions2(signal)),
|
|
10737
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES,
|
|
10738
|
+
signal
|
|
10739
|
+
);
|
|
10740
|
+
}
|
|
10741
|
+
}
|
|
10742
|
+
function isTerminalStatus2(status) {
|
|
10743
|
+
return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete";
|
|
10744
|
+
}
|
|
10745
|
+
function resumeOpenAIResearch(client, ref, spec, signal) {
|
|
10746
|
+
const streaming = spec?.capabilities.streaming ?? true;
|
|
10747
|
+
if (streaming) {
|
|
10748
|
+
return (async function* () {
|
|
10749
|
+
const current = await withRetries2(
|
|
10750
|
+
() => client.responses.retrieve(ref.jobId, {}, requestOptions2(signal)),
|
|
10751
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES,
|
|
10752
|
+
signal
|
|
10753
|
+
);
|
|
10754
|
+
if (isTerminalStatus2(mapResponseStatus(current.status))) {
|
|
10755
|
+
yield { type: "status", status: mapResponseStatus(current.status) };
|
|
10756
|
+
yield* emitTerminalResponse(current);
|
|
10757
|
+
return;
|
|
10758
|
+
}
|
|
10759
|
+
const stream2 = await client.responses.retrieve(
|
|
10760
|
+
ref.jobId,
|
|
10761
|
+
{
|
|
10762
|
+
stream: true,
|
|
10763
|
+
...ref.cursor !== void 0 ? { starting_after: Number(ref.cursor) } : {}
|
|
10764
|
+
},
|
|
10765
|
+
requestOptions2(signal)
|
|
10766
|
+
);
|
|
10767
|
+
yield* normalizeResponsesStream(stream2);
|
|
10768
|
+
})();
|
|
10769
|
+
}
|
|
10770
|
+
return pollResponseToCompletion(client, ref.jobId, void 0, signal);
|
|
10771
|
+
}
|
|
10772
|
+
async function getOpenAIResearchStatus(client, ref) {
|
|
10773
|
+
const response = await client.responses.retrieve(ref.jobId, {});
|
|
10774
|
+
const status = mapResponseStatus(response.status);
|
|
10775
|
+
if (!isTerminalStatus2(status)) {
|
|
10776
|
+
return { status };
|
|
10777
|
+
}
|
|
10778
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
10779
|
+
return {
|
|
10780
|
+
status,
|
|
10781
|
+
result: {
|
|
10782
|
+
jobId: response.id,
|
|
10783
|
+
provider: "openai",
|
|
10784
|
+
model: ref.model,
|
|
10785
|
+
status,
|
|
10786
|
+
report,
|
|
10787
|
+
citations,
|
|
10788
|
+
usage: usageFromResponse(response),
|
|
10789
|
+
raw: response
|
|
10790
|
+
}
|
|
10791
|
+
};
|
|
10792
|
+
}
|
|
10793
|
+
async function cancelOpenAIResearch(client, ref) {
|
|
10794
|
+
await client.responses.cancel(ref.jobId);
|
|
10795
|
+
}
|
|
10796
|
+
var OPENAI_FILE_SEARCH_MAX_VECTOR_STORES, OPENAI_RESEARCH_CREATE_MAX_RETRIES, OPENAI_RESEARCH_POLL_MAX_RETRIES, STATUS_MAP, requestOptions2;
|
|
10797
|
+
var init_openai_research = __esm({
|
|
10798
|
+
"src/providers/openai-research.ts"() {
|
|
10799
|
+
"use strict";
|
|
10800
|
+
init_errors2();
|
|
10801
|
+
init_retry();
|
|
10802
|
+
init_constants3();
|
|
10803
|
+
init_errors3();
|
|
10804
|
+
OPENAI_FILE_SEARCH_MAX_VECTOR_STORES = 2;
|
|
10805
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES = 3;
|
|
10806
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES = 5;
|
|
10807
|
+
STATUS_MAP = {
|
|
10808
|
+
queued: "queued",
|
|
10809
|
+
in_progress: "in_progress",
|
|
10810
|
+
completed: "completed",
|
|
10811
|
+
failed: "failed",
|
|
10812
|
+
cancelled: "cancelled",
|
|
10813
|
+
incomplete: "incomplete"
|
|
10814
|
+
};
|
|
10815
|
+
requestOptions2 = (signal) => ({
|
|
10816
|
+
timeout: OPENAI_RESEARCH_HTTP_TIMEOUT_MS,
|
|
10817
|
+
signal
|
|
10818
|
+
});
|
|
10819
|
+
}
|
|
10820
|
+
});
|
|
10821
|
+
|
|
10822
|
+
// src/providers/openai-research-models.ts
|
|
10823
|
+
function o3DeepResearch(modelId) {
|
|
10824
|
+
return {
|
|
10825
|
+
provider: "openai",
|
|
10826
|
+
modelId,
|
|
10827
|
+
kind: "model",
|
|
10828
|
+
displayName: "o3 Deep Research",
|
|
10829
|
+
contextWindow: 2e5,
|
|
10830
|
+
maxOutputTokens: 1e5,
|
|
10831
|
+
pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
|
|
10832
|
+
capabilities: {
|
|
10833
|
+
streaming: true,
|
|
10834
|
+
background: true,
|
|
10835
|
+
resumable: true,
|
|
10836
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
10837
|
+
},
|
|
10838
|
+
requiredTools: [{ type: "web_search" }],
|
|
10839
|
+
metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
|
|
10840
|
+
};
|
|
10841
|
+
}
|
|
10842
|
+
function o4MiniDeepResearch(modelId) {
|
|
10843
|
+
return {
|
|
10844
|
+
provider: "openai",
|
|
10845
|
+
modelId,
|
|
10846
|
+
kind: "model",
|
|
10847
|
+
displayName: "o4-mini Deep Research",
|
|
10848
|
+
contextWindow: 2e5,
|
|
10849
|
+
maxOutputTokens: 1e5,
|
|
10850
|
+
pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
|
|
10851
|
+
capabilities: {
|
|
10852
|
+
streaming: true,
|
|
10853
|
+
background: true,
|
|
10854
|
+
resumable: true,
|
|
10855
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
10856
|
+
},
|
|
10857
|
+
requiredTools: [{ type: "web_search" }],
|
|
10858
|
+
metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
|
|
10859
|
+
};
|
|
10860
|
+
}
|
|
10861
|
+
function getOpenAIResearchModelSpec(modelId) {
|
|
10862
|
+
return byId2.get(modelId);
|
|
10863
|
+
}
|
|
10864
|
+
function isOpenAIResearchModel(modelId) {
|
|
10865
|
+
return byId2.has(modelId);
|
|
10866
|
+
}
|
|
10867
|
+
var DEEP_RESEARCH_TOOLS, DEEP_RESEARCH_SHUTDOWN, openaiResearchModels, byId2;
|
|
10868
|
+
var init_openai_research_models = __esm({
|
|
10869
|
+
"src/providers/openai-research-models.ts"() {
|
|
10870
|
+
"use strict";
|
|
10871
|
+
DEEP_RESEARCH_TOOLS = [
|
|
10872
|
+
"web_search",
|
|
10873
|
+
"file_search",
|
|
10874
|
+
"mcp",
|
|
10875
|
+
"code_interpreter"
|
|
10876
|
+
];
|
|
10877
|
+
DEEP_RESEARCH_SHUTDOWN = {
|
|
10878
|
+
deprecationDate: "2026-04-22",
|
|
10879
|
+
shutdownDate: "2026-07-23",
|
|
10880
|
+
replacement: "gpt-5.5-pro"
|
|
10881
|
+
};
|
|
10882
|
+
openaiResearchModels = [
|
|
10883
|
+
o3DeepResearch("o3-deep-research"),
|
|
10884
|
+
o3DeepResearch("o3-deep-research-2025-06-26"),
|
|
10885
|
+
o4MiniDeepResearch("o4-mini-deep-research"),
|
|
10886
|
+
o4MiniDeepResearch("o4-mini-deep-research-2025-06-26"),
|
|
10887
|
+
{
|
|
10888
|
+
provider: "openai",
|
|
10889
|
+
modelId: "gpt-5.5-pro",
|
|
10890
|
+
kind: "model",
|
|
10891
|
+
displayName: "GPT-5.5 Pro (research)",
|
|
10892
|
+
contextWindow: 105e4,
|
|
10893
|
+
maxOutputTokens: 128e3,
|
|
10894
|
+
pricing: { input: 30, output: 180, perThousandSearches: 10 },
|
|
10895
|
+
capabilities: {
|
|
10896
|
+
// No live streaming — runs execute via background create + poll.
|
|
10897
|
+
streaming: false,
|
|
10898
|
+
background: true,
|
|
10899
|
+
// Resumable in the attach sense: the poll loop re-attaches by job id.
|
|
10900
|
+
resumable: true,
|
|
10901
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
10902
|
+
},
|
|
10903
|
+
requiredTools: [{ type: "web_search" }],
|
|
10904
|
+
metadata: {
|
|
10905
|
+
releaseDate: "2026-04-24",
|
|
10906
|
+
notes: "Designated replacement for the o3/o4-mini deep research models."
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
10909
|
+
];
|
|
10910
|
+
byId2 = new Map(openaiResearchModels.map((spec) => [spec.modelId, spec]));
|
|
10911
|
+
}
|
|
10912
|
+
});
|
|
10913
|
+
|
|
10914
|
+
// src/providers/openai-speech-models.ts
|
|
10915
|
+
function getOpenAISpeechModelSpec(modelId) {
|
|
10916
|
+
return openaiSpeechModels.find((m) => m.modelId === modelId);
|
|
10917
|
+
}
|
|
10918
|
+
function isOpenAISpeechModel(modelId) {
|
|
10919
|
+
return openaiSpeechModels.some((m) => m.modelId === modelId);
|
|
10920
|
+
}
|
|
10921
|
+
function calculateOpenAISpeechCost(modelId, characterCount, estimatedMinutes) {
|
|
10922
|
+
const spec = getOpenAISpeechModelSpec(modelId);
|
|
10923
|
+
if (!spec) return void 0;
|
|
10924
|
+
if (spec.pricing.perCharacter !== void 0) {
|
|
10925
|
+
return characterCount * spec.pricing.perCharacter;
|
|
10926
|
+
}
|
|
10927
|
+
if (spec.pricing.perMinute !== void 0 && estimatedMinutes !== void 0) {
|
|
10928
|
+
return estimatedMinutes * spec.pricing.perMinute;
|
|
10929
|
+
}
|
|
10930
|
+
if (spec.pricing.perMinute !== void 0) {
|
|
10931
|
+
const approxMinutes = characterCount / 750;
|
|
10932
|
+
return approxMinutes * spec.pricing.perMinute;
|
|
10933
|
+
}
|
|
10934
|
+
return void 0;
|
|
10935
|
+
}
|
|
10936
|
+
var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpeechModels;
|
|
10937
|
+
var init_openai_speech_models = __esm({
|
|
10938
|
+
"src/providers/openai-speech-models.ts"() {
|
|
10939
|
+
"use strict";
|
|
10940
|
+
OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
|
|
10941
|
+
OPENAI_TTS_EXTENDED_VOICES = [
|
|
10942
|
+
...OPENAI_TTS_VOICES,
|
|
10943
|
+
"ash",
|
|
10944
|
+
"ballad",
|
|
10945
|
+
"coral",
|
|
10946
|
+
"sage",
|
|
10947
|
+
"verse"
|
|
10948
|
+
];
|
|
10949
|
+
OPENAI_TTS_FORMATS = ["mp3", "opus", "aac", "flac", "wav", "pcm"];
|
|
10950
|
+
openaiSpeechModels = [
|
|
10951
|
+
// Standard TTS models (character-based pricing)
|
|
10952
|
+
{
|
|
10953
|
+
provider: "openai",
|
|
10954
|
+
modelId: "tts-1",
|
|
10955
|
+
displayName: "TTS-1",
|
|
10956
|
+
pricing: {
|
|
10957
|
+
// $15 per 1M characters = $0.000015 per character
|
|
10958
|
+
perCharacter: 15e-6
|
|
10959
|
+
},
|
|
10960
|
+
voices: [...OPENAI_TTS_VOICES],
|
|
10961
|
+
formats: OPENAI_TTS_FORMATS,
|
|
10962
|
+
maxInputLength: 4096,
|
|
10963
|
+
defaultVoice: "alloy",
|
|
10964
|
+
defaultFormat: "mp3",
|
|
10965
|
+
features: {
|
|
10966
|
+
voiceInstructions: false
|
|
10967
|
+
}
|
|
10968
|
+
},
|
|
10969
|
+
{
|
|
10970
|
+
provider: "openai",
|
|
10971
|
+
modelId: "tts-1-1106",
|
|
10972
|
+
displayName: "TTS-1 (Nov 2023)",
|
|
10973
|
+
pricing: {
|
|
10974
|
+
perCharacter: 15e-6
|
|
10975
|
+
},
|
|
10976
|
+
voices: [...OPENAI_TTS_VOICES],
|
|
10977
|
+
formats: OPENAI_TTS_FORMATS,
|
|
10978
|
+
maxInputLength: 4096,
|
|
10979
|
+
defaultVoice: "alloy",
|
|
10980
|
+
defaultFormat: "mp3",
|
|
9784
10981
|
features: {
|
|
9785
10982
|
voiceInstructions: false
|
|
9786
10983
|
}
|
|
@@ -9869,6 +11066,8 @@ var init_openai = __esm({
|
|
|
9869
11066
|
init_constants2();
|
|
9870
11067
|
init_openai_image_models();
|
|
9871
11068
|
init_openai_models();
|
|
11069
|
+
init_openai_research();
|
|
11070
|
+
init_openai_research_models();
|
|
9872
11071
|
init_openai_speech_models();
|
|
9873
11072
|
init_utils();
|
|
9874
11073
|
ROLE_MAP2 = {
|
|
@@ -9972,6 +11171,32 @@ var init_openai = __esm({
|
|
|
9972
11171
|
format: format2
|
|
9973
11172
|
};
|
|
9974
11173
|
}
|
|
11174
|
+
// =========================================================================
|
|
11175
|
+
// Deep Research (Responses API)
|
|
11176
|
+
// =========================================================================
|
|
11177
|
+
getResearchModelSpecs() {
|
|
11178
|
+
return openaiResearchModels;
|
|
11179
|
+
}
|
|
11180
|
+
supportsResearch(modelId) {
|
|
11181
|
+
return isOpenAIResearchModel(modelId);
|
|
11182
|
+
}
|
|
11183
|
+
startResearch(options, descriptor, spec) {
|
|
11184
|
+
return startOpenAIResearch(this.client, options, descriptor.name, spec);
|
|
11185
|
+
}
|
|
11186
|
+
resumeResearch(ref, signal) {
|
|
11187
|
+
return resumeOpenAIResearch(
|
|
11188
|
+
this.client,
|
|
11189
|
+
ref,
|
|
11190
|
+
getOpenAIResearchModelSpec(ref.model),
|
|
11191
|
+
signal
|
|
11192
|
+
);
|
|
11193
|
+
}
|
|
11194
|
+
getResearchStatus(ref) {
|
|
11195
|
+
return getOpenAIResearchStatus(this.client, ref);
|
|
11196
|
+
}
|
|
11197
|
+
cancelResearch(ref) {
|
|
11198
|
+
return cancelOpenAIResearch(this.client, ref);
|
|
11199
|
+
}
|
|
9975
11200
|
buildApiRequest(options, descriptor, spec, messages) {
|
|
9976
11201
|
const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
|
|
9977
11202
|
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
@@ -10699,6 +11924,217 @@ var init_openrouter_models = __esm({
|
|
|
10699
11924
|
}
|
|
10700
11925
|
});
|
|
10701
11926
|
|
|
11927
|
+
// src/providers/openrouter-research.ts
|
|
11928
|
+
function buildOpenRouterResearchMessages(options) {
|
|
11929
|
+
if (options.background === true) {
|
|
11930
|
+
throw new ResearchValidationError(
|
|
11931
|
+
"OpenRouter has no background mode for research runs \u2014 the stream must stay open."
|
|
11932
|
+
);
|
|
11933
|
+
}
|
|
11934
|
+
if (options.tools !== void 0 && options.tools.length > 0) {
|
|
11935
|
+
throw new ResearchValidationError(
|
|
11936
|
+
"OpenRouter research models manage their tools server-side \u2014 omit the tools option."
|
|
11937
|
+
);
|
|
11938
|
+
}
|
|
11939
|
+
const messages = [];
|
|
11940
|
+
if (options.systemPrompt) {
|
|
11941
|
+
messages.push({ role: "system", content: options.systemPrompt });
|
|
11942
|
+
}
|
|
11943
|
+
messages.push({ role: "user", content: options.query });
|
|
11944
|
+
return { messages };
|
|
11945
|
+
}
|
|
11946
|
+
function citationKey(citation) {
|
|
11947
|
+
return `${citation.url}#${citation.startIndex ?? ""}`;
|
|
11948
|
+
}
|
|
11949
|
+
function mapFinishReason(reason) {
|
|
11950
|
+
switch (reason) {
|
|
11951
|
+
case "length":
|
|
11952
|
+
return "incomplete";
|
|
11953
|
+
case "error":
|
|
11954
|
+
return "failed";
|
|
11955
|
+
default:
|
|
11956
|
+
return "completed";
|
|
11957
|
+
}
|
|
11958
|
+
}
|
|
11959
|
+
async function* normalizeOpenRouterResearchStream(stream2) {
|
|
11960
|
+
let createdEmitted = false;
|
|
11961
|
+
const seenCitations = /* @__PURE__ */ new Set();
|
|
11962
|
+
const seenCitationUrls = /* @__PURE__ */ new Set();
|
|
11963
|
+
const bufferedLegacyCitations = [];
|
|
11964
|
+
let usage;
|
|
11965
|
+
let terminalStatus;
|
|
11966
|
+
let lastRaw;
|
|
11967
|
+
for await (const chunk of stream2) {
|
|
11968
|
+
lastRaw = chunk;
|
|
11969
|
+
if (!createdEmitted) {
|
|
11970
|
+
createdEmitted = true;
|
|
11971
|
+
yield { type: "created", jobId: null, rawEvent: chunk };
|
|
11972
|
+
yield { type: "status", status: "in_progress" };
|
|
11973
|
+
}
|
|
11974
|
+
const choice = chunk.choices?.[0];
|
|
11975
|
+
const delta = choice?.delta ?? {};
|
|
11976
|
+
const detailTexts = (delta.reasoning_details ?? []).filter((detail) => typeof detail.text === "string" && detail.text.length > 0).map((detail) => detail.text);
|
|
11977
|
+
if (detailTexts.length > 0) {
|
|
11978
|
+
for (const text3 of detailTexts) {
|
|
11979
|
+
yield { type: "thinking", delta: text3, rawEvent: chunk };
|
|
11980
|
+
}
|
|
11981
|
+
} else if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
|
|
11982
|
+
yield { type: "thinking", delta: delta.reasoning, rawEvent: chunk };
|
|
11983
|
+
}
|
|
11984
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
11985
|
+
yield { type: "text", delta: delta.content, rawEvent: chunk };
|
|
11986
|
+
}
|
|
11987
|
+
const annotationSources = [
|
|
11988
|
+
...delta.annotations ?? [],
|
|
11989
|
+
...choice?.message?.annotations ?? []
|
|
11990
|
+
];
|
|
11991
|
+
for (const annotation of annotationSources) {
|
|
11992
|
+
if (annotation.type !== "url_citation" || !annotation.url_citation?.url) continue;
|
|
11993
|
+
const citation = {
|
|
11994
|
+
url: annotation.url_citation.url,
|
|
11995
|
+
title: annotation.url_citation.title,
|
|
11996
|
+
content: annotation.url_citation.content,
|
|
11997
|
+
startIndex: annotation.url_citation.start_index,
|
|
11998
|
+
endIndex: annotation.url_citation.end_index
|
|
11999
|
+
};
|
|
12000
|
+
const key = citationKey(citation);
|
|
12001
|
+
if (!seenCitations.has(key)) {
|
|
12002
|
+
seenCitations.add(key);
|
|
12003
|
+
seenCitationUrls.add(citation.url);
|
|
12004
|
+
yield { type: "citation", citation, rawEvent: chunk };
|
|
12005
|
+
}
|
|
12006
|
+
}
|
|
12007
|
+
const legacyCitations = chunk.citations;
|
|
12008
|
+
if (Array.isArray(legacyCitations)) {
|
|
12009
|
+
for (const url of legacyCitations) {
|
|
12010
|
+
bufferedLegacyCitations.push({ url, rawEvent: chunk });
|
|
12011
|
+
}
|
|
12012
|
+
}
|
|
12013
|
+
if (choice?.finish_reason) {
|
|
12014
|
+
terminalStatus = mapFinishReason(choice.finish_reason);
|
|
12015
|
+
}
|
|
12016
|
+
const chunkUsage = chunk.usage;
|
|
12017
|
+
if (chunkUsage) {
|
|
12018
|
+
usage = {
|
|
12019
|
+
inputTokens: chunkUsage.prompt_tokens ?? 0,
|
|
12020
|
+
outputTokens: chunkUsage.completion_tokens ?? 0,
|
|
12021
|
+
totalTokens: chunkUsage.total_tokens ?? 0,
|
|
12022
|
+
cachedInputTokens: chunkUsage.prompt_tokens_details?.cached_tokens ?? void 0,
|
|
12023
|
+
reasoningTokens: chunkUsage.completion_tokens_details?.reasoning_tokens ?? void 0,
|
|
12024
|
+
searches: chunkUsage.num_search_queries ?? chunkUsage.server_tool_use?.web_search_requests ?? void 0,
|
|
12025
|
+
// Authoritative billed cost from OpenRouter usage accounting —
|
|
12026
|
+
// preferred over the catalog estimate (collector keeps it).
|
|
12027
|
+
costUSD: typeof chunkUsage.cost === "number" ? chunkUsage.cost : void 0
|
|
12028
|
+
};
|
|
12029
|
+
}
|
|
12030
|
+
}
|
|
12031
|
+
for (const { url, rawEvent } of bufferedLegacyCitations) {
|
|
12032
|
+
if (seenCitationUrls.has(url)) continue;
|
|
12033
|
+
seenCitationUrls.add(url);
|
|
12034
|
+
yield { type: "citation", citation: { url }, rawEvent };
|
|
12035
|
+
}
|
|
12036
|
+
if (usage) {
|
|
12037
|
+
yield { type: "usage", usage };
|
|
12038
|
+
}
|
|
12039
|
+
yield {
|
|
12040
|
+
type: "done",
|
|
12041
|
+
result: {
|
|
12042
|
+
status: terminalStatus ?? "completed",
|
|
12043
|
+
// Report text was streamed as deltas; the collector joins them.
|
|
12044
|
+
report: "",
|
|
12045
|
+
usage,
|
|
12046
|
+
raw: lastRaw
|
|
12047
|
+
}
|
|
12048
|
+
};
|
|
12049
|
+
}
|
|
12050
|
+
var init_openrouter_research = __esm({
|
|
12051
|
+
"src/providers/openrouter-research.ts"() {
|
|
12052
|
+
"use strict";
|
|
12053
|
+
init_errors3();
|
|
12054
|
+
}
|
|
12055
|
+
});
|
|
12056
|
+
|
|
12057
|
+
// src/providers/openrouter-research-models.ts
|
|
12058
|
+
function isOpenRouterResearchModel(modelId) {
|
|
12059
|
+
return byId3.has(modelId);
|
|
12060
|
+
}
|
|
12061
|
+
var OPENROUTER_STREAM_ONLY, openrouterResearchModels, byId3;
|
|
12062
|
+
var init_openrouter_research_models = __esm({
|
|
12063
|
+
"src/providers/openrouter-research-models.ts"() {
|
|
12064
|
+
"use strict";
|
|
12065
|
+
OPENROUTER_STREAM_ONLY = {
|
|
12066
|
+
streaming: true,
|
|
12067
|
+
background: false,
|
|
12068
|
+
resumable: false,
|
|
12069
|
+
tools: []
|
|
12070
|
+
};
|
|
12071
|
+
openrouterResearchModels = [
|
|
12072
|
+
{
|
|
12073
|
+
provider: "openrouter",
|
|
12074
|
+
modelId: "perplexity/sonar-deep-research",
|
|
12075
|
+
kind: "model",
|
|
12076
|
+
displayName: "Perplexity Sonar Deep Research",
|
|
12077
|
+
contextWindow: 128e3,
|
|
12078
|
+
pricing: {
|
|
12079
|
+
input: 2,
|
|
12080
|
+
output: 8,
|
|
12081
|
+
// Internal reasoning tokens are priced separately from output.
|
|
12082
|
+
internalReasoning: 3,
|
|
12083
|
+
perThousandSearches: 5
|
|
12084
|
+
},
|
|
12085
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
12086
|
+
metadata: {
|
|
12087
|
+
notes: "R1-style think-then-write: expect a long reasoning stream (100k+ tokens) before report text."
|
|
12088
|
+
}
|
|
12089
|
+
},
|
|
12090
|
+
{
|
|
12091
|
+
provider: "openrouter",
|
|
12092
|
+
modelId: "perplexity/sonar-pro-search",
|
|
12093
|
+
kind: "model",
|
|
12094
|
+
displayName: "Perplexity Sonar Pro Search",
|
|
12095
|
+
contextWindow: 2e5,
|
|
12096
|
+
pricing: { input: 3, output: 15, perThousandSearches: 18 },
|
|
12097
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
12098
|
+
metadata: { notes: "Agentic multi-step Pro Search \u2014 lighter than full deep research." }
|
|
12099
|
+
},
|
|
12100
|
+
{
|
|
12101
|
+
provider: "openrouter",
|
|
12102
|
+
modelId: "openai/o3-deep-research",
|
|
12103
|
+
kind: "model",
|
|
12104
|
+
displayName: "OpenAI o3 Deep Research (via OpenRouter)",
|
|
12105
|
+
contextWindow: 2e5,
|
|
12106
|
+
maxOutputTokens: 1e5,
|
|
12107
|
+
pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
|
|
12108
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
12109
|
+
metadata: {
|
|
12110
|
+
// Upstream OpenAI shutdown propagates through OpenRouter.
|
|
12111
|
+
deprecationDate: "2026-04-22",
|
|
12112
|
+
shutdownDate: "2026-07-23",
|
|
12113
|
+
replacement: "perplexity/sonar-deep-research",
|
|
12114
|
+
notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
|
|
12115
|
+
}
|
|
12116
|
+
},
|
|
12117
|
+
{
|
|
12118
|
+
provider: "openrouter",
|
|
12119
|
+
modelId: "openai/o4-mini-deep-research",
|
|
12120
|
+
kind: "model",
|
|
12121
|
+
displayName: "OpenAI o4-mini Deep Research (via OpenRouter)",
|
|
12122
|
+
contextWindow: 2e5,
|
|
12123
|
+
maxOutputTokens: 1e5,
|
|
12124
|
+
pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
|
|
12125
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
12126
|
+
metadata: {
|
|
12127
|
+
deprecationDate: "2026-04-22",
|
|
12128
|
+
shutdownDate: "2026-07-23",
|
|
12129
|
+
replacement: "perplexity/sonar-deep-research",
|
|
12130
|
+
notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
|
|
12131
|
+
}
|
|
12132
|
+
}
|
|
12133
|
+
];
|
|
12134
|
+
byId3 = new Map(openrouterResearchModels.map((spec) => [spec.modelId, spec]));
|
|
12135
|
+
}
|
|
12136
|
+
});
|
|
12137
|
+
|
|
10702
12138
|
// src/providers/openrouter-speech-models.ts
|
|
10703
12139
|
function getOpenRouterSpeechModelSpec(modelId) {
|
|
10704
12140
|
return openrouterSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -10826,8 +12262,11 @@ var init_openrouter = __esm({
|
|
|
10826
12262
|
"use strict";
|
|
10827
12263
|
init_model_shortcuts();
|
|
10828
12264
|
init_logger();
|
|
12265
|
+
init_constants3();
|
|
10829
12266
|
init_openai_compatible_provider();
|
|
10830
12267
|
init_openrouter_models();
|
|
12268
|
+
init_openrouter_research();
|
|
12269
|
+
init_openrouter_research_models();
|
|
10831
12270
|
init_openrouter_speech_models();
|
|
10832
12271
|
init_utils();
|
|
10833
12272
|
logger = createLogger({ name: "openrouter" });
|
|
@@ -10847,6 +12286,66 @@ var init_openrouter = __esm({
|
|
|
10847
12286
|
getModelSpecs() {
|
|
10848
12287
|
return OPENROUTER_MODELS;
|
|
10849
12288
|
}
|
|
12289
|
+
// =========================================================================
|
|
12290
|
+
// Deep Research (chat-completions surface)
|
|
12291
|
+
//
|
|
12292
|
+
// Stream-only: no background mode, no job id, no resume. resumeResearch /
|
|
12293
|
+
// getResearchStatus / cancelResearch are deliberately NOT implemented — the
|
|
12294
|
+
// research job surfaces typed errors for those operations, and a dropped
|
|
12295
|
+
// stream is never silently re-run (a multi-dollar research run).
|
|
12296
|
+
// =========================================================================
|
|
12297
|
+
getResearchModelSpecs() {
|
|
12298
|
+
return openrouterResearchModels;
|
|
12299
|
+
}
|
|
12300
|
+
supportsResearch(modelId) {
|
|
12301
|
+
return isOpenRouterResearchModel(modelId);
|
|
12302
|
+
}
|
|
12303
|
+
startResearch(options, descriptor, _spec) {
|
|
12304
|
+
const { messages } = buildOpenRouterResearchMessages(options);
|
|
12305
|
+
const client = this.client;
|
|
12306
|
+
const request = {
|
|
12307
|
+
model: descriptor.name,
|
|
12308
|
+
messages,
|
|
12309
|
+
// Mandatory: multi-minute runs hit idle disconnects without streaming.
|
|
12310
|
+
stream: true,
|
|
12311
|
+
stream_options: { include_usage: true },
|
|
12312
|
+
// OpenRouter usage accounting: report the authoritative billed cost
|
|
12313
|
+
// (covers per-search fees that token-based estimation cannot see).
|
|
12314
|
+
usage: { include: true }
|
|
12315
|
+
};
|
|
12316
|
+
if (options.reasoning?.enabled !== void 0) {
|
|
12317
|
+
request.reasoning = {
|
|
12318
|
+
effort: OPENROUTER_EFFORT_MAP[options.reasoning.effort ?? "medium"]
|
|
12319
|
+
};
|
|
12320
|
+
}
|
|
12321
|
+
const protectedKeys = /* @__PURE__ */ new Set(["model", "messages", "stream", "stream_options", "usage"]);
|
|
12322
|
+
Object.assign(request, this.buildProviderSpecificParams(options.extra));
|
|
12323
|
+
for (const [key, value] of Object.entries(options.extra ?? {})) {
|
|
12324
|
+
if (!this.isProviderSpecificKey(key) && !protectedKeys.has(key)) {
|
|
12325
|
+
request[key] = value;
|
|
12326
|
+
}
|
|
12327
|
+
}
|
|
12328
|
+
const headers = this.getCustomHeaders();
|
|
12329
|
+
const requestOptions3 = {
|
|
12330
|
+
signal: options.signal,
|
|
12331
|
+
// Research runs take minutes — override the client's default timeout.
|
|
12332
|
+
timeout: OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS,
|
|
12333
|
+
...Object.keys(headers).length > 0 ? { headers } : {}
|
|
12334
|
+
};
|
|
12335
|
+
const enhance = (error) => this.enhanceError(error);
|
|
12336
|
+
return (async function* () {
|
|
12337
|
+
let stream2;
|
|
12338
|
+
try {
|
|
12339
|
+
stream2 = await client.chat.completions.create(
|
|
12340
|
+
request,
|
|
12341
|
+
requestOptions3
|
|
12342
|
+
);
|
|
12343
|
+
} catch (error) {
|
|
12344
|
+
throw enhance(error);
|
|
12345
|
+
}
|
|
12346
|
+
yield* normalizeOpenRouterResearchStream(stream2);
|
|
12347
|
+
})();
|
|
12348
|
+
}
|
|
10850
12349
|
/**
|
|
10851
12350
|
* Override buildApiRequest to inject reasoning parameters and cache_control breakpoints.
|
|
10852
12351
|
* OpenRouter normalizes reasoning into the standard OpenAI format,
|
|
@@ -11146,6 +12645,590 @@ var init_discovery = __esm({
|
|
|
11146
12645
|
}
|
|
11147
12646
|
});
|
|
11148
12647
|
|
|
12648
|
+
// src/research/cost.ts
|
|
12649
|
+
function estimateResearchCost(pricing, usage) {
|
|
12650
|
+
const cachedTokens = usage.cachedInputTokens ?? 0;
|
|
12651
|
+
const freshInputTokens = Math.max(0, usage.inputTokens - cachedTokens);
|
|
12652
|
+
const cachedRate = pricing.cachedInput ?? pricing.input;
|
|
12653
|
+
let outputTokens = usage.outputTokens;
|
|
12654
|
+
let reasoningCost = 0;
|
|
12655
|
+
if (pricing.internalReasoning !== void 0 && usage.reasoningTokens !== void 0) {
|
|
12656
|
+
outputTokens = Math.max(0, outputTokens - usage.reasoningTokens);
|
|
12657
|
+
reasoningCost = usage.reasoningTokens * pricing.internalReasoning / TOKENS_PER_MILLION;
|
|
12658
|
+
}
|
|
12659
|
+
const searchCost = pricing.perThousandSearches !== void 0 && usage.searches !== void 0 ? usage.searches * pricing.perThousandSearches / SEARCHES_PER_THOUSAND : 0;
|
|
12660
|
+
const total = freshInputTokens * pricing.input / TOKENS_PER_MILLION + cachedTokens * cachedRate / TOKENS_PER_MILLION + outputTokens * pricing.output / TOKENS_PER_MILLION + reasoningCost + searchCost;
|
|
12661
|
+
return Number(total.toFixed(RESEARCH_COST_DECIMALS));
|
|
12662
|
+
}
|
|
12663
|
+
var init_cost = __esm({
|
|
12664
|
+
"src/research/cost.ts"() {
|
|
12665
|
+
"use strict";
|
|
12666
|
+
init_constants3();
|
|
12667
|
+
}
|
|
12668
|
+
});
|
|
12669
|
+
|
|
12670
|
+
// src/research/collector.ts
|
|
12671
|
+
function citationKey2(citation) {
|
|
12672
|
+
return `${citation.url}#${citation.startIndex ?? ""}`;
|
|
12673
|
+
}
|
|
12674
|
+
function max(a, b) {
|
|
12675
|
+
if (a === void 0) return b;
|
|
12676
|
+
if (b === void 0) return a;
|
|
12677
|
+
return Math.max(a, b);
|
|
12678
|
+
}
|
|
12679
|
+
var EMPTY_USAGE, TERMINAL_STATUSES, ResearchResultCollector;
|
|
12680
|
+
var init_collector = __esm({
|
|
12681
|
+
"src/research/collector.ts"() {
|
|
12682
|
+
"use strict";
|
|
12683
|
+
init_cost();
|
|
12684
|
+
EMPTY_USAGE = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
12685
|
+
TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
12686
|
+
"completed",
|
|
12687
|
+
"failed",
|
|
12688
|
+
"cancelled",
|
|
12689
|
+
"incomplete",
|
|
12690
|
+
"budget_exceeded"
|
|
12691
|
+
]);
|
|
12692
|
+
ResearchResultCollector = class {
|
|
12693
|
+
constructor(spec, now = Date.now) {
|
|
12694
|
+
this.spec = spec;
|
|
12695
|
+
this.now = now;
|
|
12696
|
+
}
|
|
12697
|
+
spec;
|
|
12698
|
+
now;
|
|
12699
|
+
reportParts = [];
|
|
12700
|
+
citations = /* @__PURE__ */ new Map();
|
|
12701
|
+
usage;
|
|
12702
|
+
lastStatus;
|
|
12703
|
+
doneReport = "";
|
|
12704
|
+
doneRaw;
|
|
12705
|
+
hasDone = false;
|
|
12706
|
+
firstEventAt;
|
|
12707
|
+
terminalAt;
|
|
12708
|
+
/**
|
|
12709
|
+
* Terminal error info from an `error` event.
|
|
12710
|
+
* @internal Read by tests only — not part of the public consumer contract.
|
|
12711
|
+
*/
|
|
12712
|
+
terminalError;
|
|
12713
|
+
ingest(event) {
|
|
12714
|
+
this.firstEventAt ??= this.now();
|
|
12715
|
+
switch (event.type) {
|
|
12716
|
+
case "text":
|
|
12717
|
+
this.reportParts.push(event.delta);
|
|
12718
|
+
break;
|
|
12719
|
+
case "citation":
|
|
12720
|
+
this.addCitation(event.citation);
|
|
12721
|
+
break;
|
|
12722
|
+
case "usage":
|
|
12723
|
+
this.mergeUsage(event.usage);
|
|
12724
|
+
break;
|
|
12725
|
+
case "status":
|
|
12726
|
+
this.lastStatus = event.status;
|
|
12727
|
+
break;
|
|
12728
|
+
case "error":
|
|
12729
|
+
this.terminalError = event.error;
|
|
12730
|
+
this.terminalAt = this.now();
|
|
12731
|
+
break;
|
|
12732
|
+
case "done": {
|
|
12733
|
+
this.hasDone = true;
|
|
12734
|
+
this.lastStatus = event.result.status;
|
|
12735
|
+
this.doneReport = event.result.report;
|
|
12736
|
+
this.doneRaw = event.result.raw;
|
|
12737
|
+
for (const citation of event.result.citations ?? []) {
|
|
12738
|
+
this.addCitation(citation);
|
|
12739
|
+
}
|
|
12740
|
+
if (event.result.usage) {
|
|
12741
|
+
this.mergeUsage(event.result.usage);
|
|
12742
|
+
}
|
|
12743
|
+
this.terminalAt = this.now();
|
|
12744
|
+
break;
|
|
12745
|
+
}
|
|
12746
|
+
default:
|
|
12747
|
+
break;
|
|
12748
|
+
}
|
|
12749
|
+
}
|
|
12750
|
+
toResult(context) {
|
|
12751
|
+
const usage = { ...this.usage ?? EMPTY_USAGE };
|
|
12752
|
+
if (usage.costUSD === void 0 && this.spec) {
|
|
12753
|
+
usage.costUSD = estimateResearchCost(this.spec.pricing, usage);
|
|
12754
|
+
}
|
|
12755
|
+
const status = this.lastStatus ?? (this.terminalError ? "failed" : "in_progress");
|
|
12756
|
+
const lastIsTerminal = this.lastStatus !== void 0 && TERMINAL_STATUSES.has(this.lastStatus);
|
|
12757
|
+
return {
|
|
12758
|
+
jobId: context.jobId,
|
|
12759
|
+
provider: context.provider,
|
|
12760
|
+
model: context.model,
|
|
12761
|
+
// An error fails the run only when no explicit terminal status preceded
|
|
12762
|
+
// it (a timeout's "incomplete" is a partial, not a failure).
|
|
12763
|
+
status: this.terminalError && !this.hasDone && !lastIsTerminal ? "failed" : status,
|
|
12764
|
+
report: this.doneReport !== "" ? this.doneReport : this.reportParts.join(""),
|
|
12765
|
+
citations: [...this.citations.values()],
|
|
12766
|
+
usage,
|
|
12767
|
+
durationMs: this.firstEventAt !== void 0 && this.terminalAt !== void 0 ? this.terminalAt - this.firstEventAt : void 0,
|
|
12768
|
+
raw: this.doneRaw
|
|
12769
|
+
};
|
|
12770
|
+
}
|
|
12771
|
+
/** Whether a terminal event (`done` or `error`) was ingested. */
|
|
12772
|
+
get isTerminal() {
|
|
12773
|
+
return this.hasDone || this.terminalError !== void 0;
|
|
12774
|
+
}
|
|
12775
|
+
addCitation(citation) {
|
|
12776
|
+
const key = citationKey2(citation);
|
|
12777
|
+
if (!this.citations.has(key)) {
|
|
12778
|
+
this.citations.set(key, citation);
|
|
12779
|
+
}
|
|
12780
|
+
}
|
|
12781
|
+
mergeUsage(incoming) {
|
|
12782
|
+
const current = this.usage ?? { ...EMPTY_USAGE };
|
|
12783
|
+
this.usage = {
|
|
12784
|
+
...current,
|
|
12785
|
+
...incoming,
|
|
12786
|
+
// Cumulative counters may be re-reported; never let them regress.
|
|
12787
|
+
searches: max(current.searches, incoming.searches)
|
|
12788
|
+
};
|
|
12789
|
+
}
|
|
12790
|
+
};
|
|
12791
|
+
}
|
|
12792
|
+
});
|
|
12793
|
+
|
|
12794
|
+
// src/research/job.ts
|
|
12795
|
+
var ResearchJobImpl;
|
|
12796
|
+
var init_job = __esm({
|
|
12797
|
+
"src/research/job.ts"() {
|
|
12798
|
+
"use strict";
|
|
12799
|
+
init_errors2();
|
|
12800
|
+
init_collector();
|
|
12801
|
+
init_constants3();
|
|
12802
|
+
init_errors3();
|
|
12803
|
+
ResearchJobImpl = class {
|
|
12804
|
+
adapter;
|
|
12805
|
+
descriptor;
|
|
12806
|
+
spec;
|
|
12807
|
+
options;
|
|
12808
|
+
resumeFrom;
|
|
12809
|
+
collector;
|
|
12810
|
+
controller = new AbortController();
|
|
12811
|
+
timeoutMs;
|
|
12812
|
+
state = "idle";
|
|
12813
|
+
currentJobId;
|
|
12814
|
+
cursor;
|
|
12815
|
+
startedAt;
|
|
12816
|
+
timedOut = false;
|
|
12817
|
+
cancelRequested = false;
|
|
12818
|
+
finalResult;
|
|
12819
|
+
failure;
|
|
12820
|
+
completion;
|
|
12821
|
+
constructor(init) {
|
|
12822
|
+
this.adapter = init.adapter;
|
|
12823
|
+
this.descriptor = init.descriptor;
|
|
12824
|
+
this.spec = init.spec;
|
|
12825
|
+
this.options = init.options;
|
|
12826
|
+
this.resumeFrom = init.resumeFrom;
|
|
12827
|
+
this.collector = new ResearchResultCollector(init.spec);
|
|
12828
|
+
this.currentJobId = init.resumeFrom?.jobId ?? null;
|
|
12829
|
+
this.cursor = init.resumeFrom?.cursor;
|
|
12830
|
+
this.startedAt = init.resumeFrom?.startedAt;
|
|
12831
|
+
const specCap = init.spec?.maxDurationMs ?? Number.POSITIVE_INFINITY;
|
|
12832
|
+
this.timeoutMs = init.options?.timeoutMs ?? Math.min(RESEARCH_DEFAULT_TIMEOUT_MS, specCap);
|
|
12833
|
+
if (init.options?.signal) {
|
|
12834
|
+
const external = init.options.signal;
|
|
12835
|
+
if (external.aborted) {
|
|
12836
|
+
this.controller.abort(external.reason);
|
|
12837
|
+
} else {
|
|
12838
|
+
external.addEventListener("abort", () => this.controller.abort(external.reason), {
|
|
12839
|
+
once: true
|
|
12840
|
+
});
|
|
12841
|
+
}
|
|
12842
|
+
}
|
|
12843
|
+
}
|
|
12844
|
+
get jobId() {
|
|
12845
|
+
return this.currentJobId;
|
|
12846
|
+
}
|
|
12847
|
+
get provider() {
|
|
12848
|
+
return this.adapter.providerId;
|
|
12849
|
+
}
|
|
12850
|
+
get model() {
|
|
12851
|
+
return this.descriptor.name;
|
|
12852
|
+
}
|
|
12853
|
+
[Symbol.asyncIterator]() {
|
|
12854
|
+
return this.events()[Symbol.asyncIterator]();
|
|
12855
|
+
}
|
|
12856
|
+
events() {
|
|
12857
|
+
if (this.state !== "idle") {
|
|
12858
|
+
throw new ResearchStreamConsumedError();
|
|
12859
|
+
}
|
|
12860
|
+
this.state = "streaming";
|
|
12861
|
+
let resolveCompletion = () => {
|
|
12862
|
+
};
|
|
12863
|
+
const promise = new Promise((resolve2) => {
|
|
12864
|
+
resolveCompletion = resolve2;
|
|
12865
|
+
});
|
|
12866
|
+
this.completion = { promise, resolve: resolveCompletion };
|
|
12867
|
+
return this.run();
|
|
12868
|
+
}
|
|
12869
|
+
async result() {
|
|
12870
|
+
if (this.state === "idle") {
|
|
12871
|
+
for await (const _event of this.events()) {
|
|
12872
|
+
}
|
|
12873
|
+
} else if (this.state === "streaming" && this.completion) {
|
|
12874
|
+
await this.completion.promise;
|
|
12875
|
+
}
|
|
12876
|
+
if (this.failure) {
|
|
12877
|
+
throw this.failure;
|
|
12878
|
+
}
|
|
12879
|
+
this.finalResult ??= this.collector.toResult(this.resultContext());
|
|
12880
|
+
return this.finalResult;
|
|
12881
|
+
}
|
|
12882
|
+
async status() {
|
|
12883
|
+
if (!this.adapter.getResearchStatus) {
|
|
12884
|
+
throw new ResearchNotPollableError(
|
|
12885
|
+
`Provider "${this.provider}" does not support research status polling.`
|
|
12886
|
+
);
|
|
12887
|
+
}
|
|
12888
|
+
if (this.currentJobId === null) {
|
|
12889
|
+
throw new ResearchNotPollableError(
|
|
12890
|
+
`Research run on "${this.provider}" has no server-side job id to poll.`
|
|
12891
|
+
);
|
|
12892
|
+
}
|
|
12893
|
+
const snapshot = await this.adapter.getResearchStatus(this.buildRef());
|
|
12894
|
+
return snapshot.status;
|
|
12895
|
+
}
|
|
12896
|
+
async cancel() {
|
|
12897
|
+
this.cancelRequested = true;
|
|
12898
|
+
if (this.adapter.cancelResearch && this.currentJobId !== null) {
|
|
12899
|
+
await this.adapter.cancelResearch(this.buildRef());
|
|
12900
|
+
}
|
|
12901
|
+
this.controller.abort();
|
|
12902
|
+
}
|
|
12903
|
+
toRef() {
|
|
12904
|
+
if (this.currentJobId === null) {
|
|
12905
|
+
throw new ResearchJobNotResumableError(
|
|
12906
|
+
`Research run on "${this.provider}" has no server-side job id \u2014 the provider does not support background jobs, so it cannot be re-attached.`
|
|
12907
|
+
);
|
|
12908
|
+
}
|
|
12909
|
+
return this.buildRef();
|
|
12910
|
+
}
|
|
12911
|
+
buildRef() {
|
|
12912
|
+
if (this.currentJobId === null) {
|
|
12913
|
+
throw new ResearchJobNotResumableError("Research job has no server-side job id.");
|
|
12914
|
+
}
|
|
12915
|
+
return {
|
|
12916
|
+
provider: this.provider,
|
|
12917
|
+
model: this.descriptor.name,
|
|
12918
|
+
jobId: this.currentJobId,
|
|
12919
|
+
cursor: this.cursor,
|
|
12920
|
+
startedAt: this.startedAt
|
|
12921
|
+
};
|
|
12922
|
+
}
|
|
12923
|
+
resultContext() {
|
|
12924
|
+
return { provider: this.provider, model: this.descriptor.name, jobId: this.currentJobId };
|
|
12925
|
+
}
|
|
12926
|
+
openStream(resume) {
|
|
12927
|
+
if (resume) {
|
|
12928
|
+
if (!this.adapter.resumeResearch) {
|
|
12929
|
+
throw new ResearchJobNotResumableError(
|
|
12930
|
+
`Provider "${this.provider}" does not support resuming research jobs.`
|
|
12931
|
+
);
|
|
12932
|
+
}
|
|
12933
|
+
return this.adapter.resumeResearch(this.buildRef(), this.controller.signal);
|
|
12934
|
+
}
|
|
12935
|
+
if (!this.adapter.startResearch || !this.options) {
|
|
12936
|
+
throw new ResearchJobNotResumableError(
|
|
12937
|
+
`Provider "${this.provider}" cannot start this research job.`
|
|
12938
|
+
);
|
|
12939
|
+
}
|
|
12940
|
+
return this.adapter.startResearch(
|
|
12941
|
+
{ ...this.options, signal: this.controller.signal },
|
|
12942
|
+
this.descriptor,
|
|
12943
|
+
this.spec
|
|
12944
|
+
);
|
|
12945
|
+
}
|
|
12946
|
+
observe(event) {
|
|
12947
|
+
if (event.cursor !== void 0) {
|
|
12948
|
+
this.cursor = event.cursor;
|
|
12949
|
+
}
|
|
12950
|
+
if (event.type === "created") {
|
|
12951
|
+
if (event.jobId !== null) {
|
|
12952
|
+
this.currentJobId = event.jobId;
|
|
12953
|
+
}
|
|
12954
|
+
this.startedAt ??= (/* @__PURE__ */ new Date()).toISOString();
|
|
12955
|
+
}
|
|
12956
|
+
this.collector.ingest(event);
|
|
12957
|
+
}
|
|
12958
|
+
canResume() {
|
|
12959
|
+
if (this.adapter.resumeResearch === void 0 || this.currentJobId === null) {
|
|
12960
|
+
return false;
|
|
12961
|
+
}
|
|
12962
|
+
return this.spec ? this.spec.capabilities.resumable : true;
|
|
12963
|
+
}
|
|
12964
|
+
async *run() {
|
|
12965
|
+
const timer = setTimeout(() => {
|
|
12966
|
+
this.timedOut = true;
|
|
12967
|
+
this.controller.abort(new ResearchTimeoutError(this.timeoutMs));
|
|
12968
|
+
}, this.timeoutMs);
|
|
12969
|
+
timer.unref?.();
|
|
12970
|
+
let reconnectAttempts = 0;
|
|
12971
|
+
let source = this.openStream(this.resumeFrom !== void 0);
|
|
12972
|
+
try {
|
|
12973
|
+
while (true) {
|
|
12974
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
12975
|
+
try {
|
|
12976
|
+
while (true) {
|
|
12977
|
+
const next = await iterator.next();
|
|
12978
|
+
if (next.done) {
|
|
12979
|
+
return;
|
|
12980
|
+
}
|
|
12981
|
+
reconnectAttempts = 0;
|
|
12982
|
+
this.observe(next.value);
|
|
12983
|
+
yield next.value;
|
|
12984
|
+
if (this.collector.isTerminal) {
|
|
12985
|
+
return;
|
|
12986
|
+
}
|
|
12987
|
+
}
|
|
12988
|
+
} catch (error) {
|
|
12989
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
12990
|
+
if (this.timedOut) {
|
|
12991
|
+
const statusEvent = { type: "status", status: "incomplete" };
|
|
12992
|
+
this.collector.ingest(statusEvent);
|
|
12993
|
+
yield statusEvent;
|
|
12994
|
+
const info2 = {
|
|
12995
|
+
message: new ResearchTimeoutError(this.timeoutMs).message,
|
|
12996
|
+
code: "timeout",
|
|
12997
|
+
retryable: false
|
|
12998
|
+
};
|
|
12999
|
+
this.collector.ingest({ type: "error", error: info2 });
|
|
13000
|
+
yield { type: "error", error: info2 };
|
|
13001
|
+
return;
|
|
13002
|
+
}
|
|
13003
|
+
if (this.cancelRequested && isAbortError(err)) {
|
|
13004
|
+
const event = { type: "status", status: "cancelled" };
|
|
13005
|
+
this.observe(event);
|
|
13006
|
+
yield event;
|
|
13007
|
+
return;
|
|
13008
|
+
}
|
|
13009
|
+
if (isAbortError(err)) {
|
|
13010
|
+
this.failure = err;
|
|
13011
|
+
throw err;
|
|
13012
|
+
}
|
|
13013
|
+
if (this.canResume() && reconnectAttempts < RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS) {
|
|
13014
|
+
reconnectAttempts += 1;
|
|
13015
|
+
source = this.openStream(true);
|
|
13016
|
+
continue;
|
|
13017
|
+
}
|
|
13018
|
+
const info = { message: err.message, retryable: false };
|
|
13019
|
+
this.collector.ingest({ type: "error", error: info });
|
|
13020
|
+
yield { type: "error", error: info };
|
|
13021
|
+
return;
|
|
13022
|
+
}
|
|
13023
|
+
}
|
|
13024
|
+
} finally {
|
|
13025
|
+
clearTimeout(timer);
|
|
13026
|
+
this.state = "finished";
|
|
13027
|
+
this.finalResult = this.collector.toResult(this.resultContext());
|
|
13028
|
+
this.completion?.resolve();
|
|
13029
|
+
}
|
|
13030
|
+
}
|
|
13031
|
+
};
|
|
13032
|
+
}
|
|
13033
|
+
});
|
|
13034
|
+
|
|
13035
|
+
// src/research/types.ts
|
|
13036
|
+
var RESEARCH_DATA_SOURCE_TOOL_TYPES;
|
|
13037
|
+
var init_types = __esm({
|
|
13038
|
+
"src/research/types.ts"() {
|
|
13039
|
+
"use strict";
|
|
13040
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES = [
|
|
13041
|
+
"web_search",
|
|
13042
|
+
"file_search",
|
|
13043
|
+
"mcp"
|
|
13044
|
+
];
|
|
13045
|
+
}
|
|
13046
|
+
});
|
|
13047
|
+
|
|
13048
|
+
// src/research/namespace.ts
|
|
13049
|
+
var ResearchNamespace;
|
|
13050
|
+
var init_namespace = __esm({
|
|
13051
|
+
"src/research/namespace.ts"() {
|
|
13052
|
+
"use strict";
|
|
13053
|
+
init_logger();
|
|
13054
|
+
init_constants3();
|
|
13055
|
+
init_errors3();
|
|
13056
|
+
init_job();
|
|
13057
|
+
init_types();
|
|
13058
|
+
ResearchNamespace = class {
|
|
13059
|
+
constructor(adapters, parser, now = Date.now, logger2) {
|
|
13060
|
+
this.adapters = adapters;
|
|
13061
|
+
this.parser = parser;
|
|
13062
|
+
this.now = now;
|
|
13063
|
+
this.logger = logger2 ?? createLogger({ name: "llmist:research" });
|
|
13064
|
+
}
|
|
13065
|
+
adapters;
|
|
13066
|
+
parser;
|
|
13067
|
+
now;
|
|
13068
|
+
logger;
|
|
13069
|
+
/**
|
|
13070
|
+
* Start a research run. Returns immediately; the provider stream opens
|
|
13071
|
+
* lazily on first iteration (or on `result()`).
|
|
13072
|
+
*/
|
|
13073
|
+
start(options) {
|
|
13074
|
+
const descriptor = this.parser.parse(options.model);
|
|
13075
|
+
const adapter = this.findResearchAdapter(descriptor);
|
|
13076
|
+
if (!adapter) {
|
|
13077
|
+
throw new ResearchNotSupportedError(
|
|
13078
|
+
`No provider supports deep research for model "${options.model}". Research-capable models: ${this.describeAvailableModels()}`
|
|
13079
|
+
);
|
|
13080
|
+
}
|
|
13081
|
+
const spec = this.findSpec(adapter, descriptor.name);
|
|
13082
|
+
const validated = spec ? this.validate(options, spec) : options;
|
|
13083
|
+
return new ResearchJobImpl({ adapter, descriptor, spec, options: validated });
|
|
13084
|
+
}
|
|
13085
|
+
/**
|
|
13086
|
+
* Re-attach to a background research job from a serialized ref.
|
|
13087
|
+
* No network happens until the returned job is iterated.
|
|
13088
|
+
*/
|
|
13089
|
+
attach(ref) {
|
|
13090
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
13091
|
+
if (!adapter) {
|
|
13092
|
+
throw new ResearchNotSupportedError(
|
|
13093
|
+
`No registered provider with id "${ref.provider}" to attach research job "${ref.jobId}".`
|
|
13094
|
+
);
|
|
13095
|
+
}
|
|
13096
|
+
if (!adapter.resumeResearch) {
|
|
13097
|
+
throw new ResearchJobNotResumableError(
|
|
13098
|
+
`Provider "${ref.provider}" does not support resuming research jobs.`
|
|
13099
|
+
);
|
|
13100
|
+
}
|
|
13101
|
+
const descriptor = { provider: ref.provider, name: ref.model };
|
|
13102
|
+
const spec = this.findSpec(adapter, ref.model);
|
|
13103
|
+
return new ResearchJobImpl({ adapter, descriptor, spec, resumeFrom: ref });
|
|
13104
|
+
}
|
|
13105
|
+
/** One-shot status poll for a job ref. */
|
|
13106
|
+
async get(ref) {
|
|
13107
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
13108
|
+
if (!adapter) {
|
|
13109
|
+
throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
|
|
13110
|
+
}
|
|
13111
|
+
if (!adapter.getResearchStatus) {
|
|
13112
|
+
throw new ResearchNotPollableError(
|
|
13113
|
+
`Provider "${ref.provider}" does not support research status polling.`
|
|
13114
|
+
);
|
|
13115
|
+
}
|
|
13116
|
+
return adapter.getResearchStatus(ref);
|
|
13117
|
+
}
|
|
13118
|
+
/** Cancel a background job server-side. */
|
|
13119
|
+
async cancel(ref) {
|
|
13120
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
13121
|
+
if (!adapter) {
|
|
13122
|
+
throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
|
|
13123
|
+
}
|
|
13124
|
+
if (!adapter.cancelResearch) {
|
|
13125
|
+
throw new ResearchNotSupportedError(
|
|
13126
|
+
`Provider "${ref.provider}" does not support cancelling research jobs.`
|
|
13127
|
+
);
|
|
13128
|
+
}
|
|
13129
|
+
return adapter.cancelResearch(ref);
|
|
13130
|
+
}
|
|
13131
|
+
/** All research-capable models/agents across registered providers. */
|
|
13132
|
+
listModels() {
|
|
13133
|
+
const specs = [];
|
|
13134
|
+
for (const adapter of this.adapters) {
|
|
13135
|
+
specs.push(...adapter.getResearchModelSpecs?.() ?? []);
|
|
13136
|
+
}
|
|
13137
|
+
return specs;
|
|
13138
|
+
}
|
|
13139
|
+
/** Whether any registered provider supports research for this model. */
|
|
13140
|
+
supportsModel(model) {
|
|
13141
|
+
try {
|
|
13142
|
+
return this.findResearchAdapter(this.parser.parse(model)) !== void 0;
|
|
13143
|
+
} catch {
|
|
13144
|
+
return false;
|
|
13145
|
+
}
|
|
13146
|
+
}
|
|
13147
|
+
findResearchAdapter(descriptor) {
|
|
13148
|
+
return this.adapters.find(
|
|
13149
|
+
(adapter) => adapter.supports(descriptor) && (adapter.supportsResearch?.(descriptor.name) ?? false)
|
|
13150
|
+
);
|
|
13151
|
+
}
|
|
13152
|
+
findAdapterByProviderId(providerId) {
|
|
13153
|
+
return this.adapters.find((adapter) => adapter.providerId === providerId);
|
|
13154
|
+
}
|
|
13155
|
+
findSpec(adapter, modelId) {
|
|
13156
|
+
return adapter.getResearchModelSpecs?.().find((spec) => spec.modelId === modelId);
|
|
13157
|
+
}
|
|
13158
|
+
describeAvailableModels() {
|
|
13159
|
+
const models = this.listModels();
|
|
13160
|
+
if (models.length === 0) {
|
|
13161
|
+
return "(none registered)";
|
|
13162
|
+
}
|
|
13163
|
+
return models.map((spec) => `${spec.provider}:${spec.modelId}`).join(", ");
|
|
13164
|
+
}
|
|
13165
|
+
/**
|
|
13166
|
+
* Pre-flight validation against the catalog spec — fails fast before any
|
|
13167
|
+
* network call and applies spec-driven defaults.
|
|
13168
|
+
*/
|
|
13169
|
+
validate(options, spec) {
|
|
13170
|
+
this.enforceLifecycle(spec);
|
|
13171
|
+
const background = options.background ?? spec.capabilities.background;
|
|
13172
|
+
if (background && !spec.capabilities.background) {
|
|
13173
|
+
throw new ResearchValidationError(
|
|
13174
|
+
`Model "${spec.modelId}" does not support background research jobs.`
|
|
13175
|
+
);
|
|
13176
|
+
}
|
|
13177
|
+
if (options.previousJobId && !spec.capabilities.followUps) {
|
|
13178
|
+
throw new ResearchValidationError(
|
|
13179
|
+
`Model "${spec.modelId}" does not support follow-up research runs (previousJobId).`
|
|
13180
|
+
);
|
|
13181
|
+
}
|
|
13182
|
+
const tools = this.resolveTools(options, spec);
|
|
13183
|
+
return { ...options, background, tools };
|
|
13184
|
+
}
|
|
13185
|
+
resolveTools(options, spec) {
|
|
13186
|
+
if (options.tools === void 0) {
|
|
13187
|
+
return spec.requiredTools;
|
|
13188
|
+
}
|
|
13189
|
+
for (const tool of options.tools) {
|
|
13190
|
+
if (!spec.capabilities.tools.includes(tool.type)) {
|
|
13191
|
+
throw new ResearchValidationError(
|
|
13192
|
+
`Model "${spec.modelId}" does not accept the "${tool.type}" tool. ` + (spec.capabilities.tools.length > 0 ? `Accepted tools: ${spec.capabilities.tools.join(", ")}.` : "This model manages its research tools itself \u2014 omit the tools option.")
|
|
13193
|
+
);
|
|
13194
|
+
}
|
|
13195
|
+
}
|
|
13196
|
+
const hasDataSource = options.tools.some(
|
|
13197
|
+
(tool) => RESEARCH_DATA_SOURCE_TOOL_TYPES.includes(tool.type)
|
|
13198
|
+
);
|
|
13199
|
+
if (!hasDataSource && spec.requiredTools) {
|
|
13200
|
+
return [...spec.requiredTools, ...options.tools];
|
|
13201
|
+
}
|
|
13202
|
+
return options.tools;
|
|
13203
|
+
}
|
|
13204
|
+
enforceLifecycle(spec) {
|
|
13205
|
+
const shutdownDate = spec.metadata?.shutdownDate;
|
|
13206
|
+
if (!shutdownDate) {
|
|
13207
|
+
return;
|
|
13208
|
+
}
|
|
13209
|
+
const shutdownMs = Date.parse(shutdownDate);
|
|
13210
|
+
if (Number.isNaN(shutdownMs)) {
|
|
13211
|
+
return;
|
|
13212
|
+
}
|
|
13213
|
+
const nowMs = this.now();
|
|
13214
|
+
if (nowMs >= shutdownMs) {
|
|
13215
|
+
throw new ResearchDeprecatedModelError({
|
|
13216
|
+
modelId: spec.modelId,
|
|
13217
|
+
shutdownDate,
|
|
13218
|
+
replacement: spec.metadata?.replacement
|
|
13219
|
+
});
|
|
13220
|
+
}
|
|
13221
|
+
const warningStartMs = shutdownMs - RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS * MS_PER_DAY;
|
|
13222
|
+
if (nowMs >= warningStartMs) {
|
|
13223
|
+
this.logger.warn(
|
|
13224
|
+
`Research model "${spec.modelId}" shuts down on ${shutdownDate}` + (spec.metadata?.replacement ? ` \u2014 migrate to "${spec.metadata.replacement}".` : ".")
|
|
13225
|
+
);
|
|
13226
|
+
}
|
|
13227
|
+
}
|
|
13228
|
+
};
|
|
13229
|
+
}
|
|
13230
|
+
});
|
|
13231
|
+
|
|
11149
13232
|
// src/core/model-registry.ts
|
|
11150
13233
|
var ModelRegistry;
|
|
11151
13234
|
var init_model_registry = __esm({
|
|
@@ -11370,6 +13453,8 @@ var init_image = __esm({
|
|
|
11370
13453
|
this.adapters = adapters;
|
|
11371
13454
|
this.defaultProvider = defaultProvider;
|
|
11372
13455
|
}
|
|
13456
|
+
adapters;
|
|
13457
|
+
defaultProvider;
|
|
11373
13458
|
/**
|
|
11374
13459
|
* Generate images from a text prompt.
|
|
11375
13460
|
*
|
|
@@ -11422,6 +13507,8 @@ var init_speech = __esm({
|
|
|
11422
13507
|
this.adapters = adapters;
|
|
11423
13508
|
this.defaultProvider = defaultProvider;
|
|
11424
13509
|
}
|
|
13510
|
+
adapters;
|
|
13511
|
+
defaultProvider;
|
|
11425
13512
|
/**
|
|
11426
13513
|
* Generate speech audio from text.
|
|
11427
13514
|
*
|
|
@@ -11517,6 +13604,7 @@ var init_text = __esm({
|
|
|
11517
13604
|
constructor(client) {
|
|
11518
13605
|
this.client = client;
|
|
11519
13606
|
}
|
|
13607
|
+
client;
|
|
11520
13608
|
/**
|
|
11521
13609
|
* Generate a complete text response.
|
|
11522
13610
|
*
|
|
@@ -11552,6 +13640,7 @@ var init_vision = __esm({
|
|
|
11552
13640
|
constructor(client) {
|
|
11553
13641
|
this.client = client;
|
|
11554
13642
|
}
|
|
13643
|
+
client;
|
|
11555
13644
|
/**
|
|
11556
13645
|
* Build a message builder with the image content attached.
|
|
11557
13646
|
* Handles URLs, data URLs, base64 strings, and binary buffers.
|
|
@@ -11678,6 +13767,7 @@ var init_options = __esm({
|
|
|
11678
13767
|
constructor(defaultProvider = "openai") {
|
|
11679
13768
|
this.defaultProvider = defaultProvider;
|
|
11680
13769
|
}
|
|
13770
|
+
defaultProvider;
|
|
11681
13771
|
parse(identifier) {
|
|
11682
13772
|
const trimmed = identifier.trim();
|
|
11683
13773
|
if (!trimmed) {
|
|
@@ -11709,6 +13799,7 @@ var init_client2 = __esm({
|
|
|
11709
13799
|
"use strict";
|
|
11710
13800
|
init_builder();
|
|
11711
13801
|
init_discovery();
|
|
13802
|
+
init_namespace();
|
|
11712
13803
|
init_constants();
|
|
11713
13804
|
init_model_registry();
|
|
11714
13805
|
init_image();
|
|
@@ -11727,6 +13818,12 @@ var init_client2 = __esm({
|
|
|
11727
13818
|
image;
|
|
11728
13819
|
speech;
|
|
11729
13820
|
vision;
|
|
13821
|
+
/**
|
|
13822
|
+
* Deep research — long-running, server-side research jobs with cited
|
|
13823
|
+
* reports (OpenAI Responses, Gemini Interactions, OpenRouter research
|
|
13824
|
+
* models). See docs/specs/002-deep-research.md.
|
|
13825
|
+
*/
|
|
13826
|
+
research;
|
|
11730
13827
|
constructor(...args) {
|
|
11731
13828
|
let adapters = [];
|
|
11732
13829
|
let defaultProvider;
|
|
@@ -11778,6 +13875,7 @@ var init_client2 = __esm({
|
|
|
11778
13875
|
this.image = new ImageNamespace(this.adapters, this.defaultProvider);
|
|
11779
13876
|
this.speech = new SpeechNamespace(this.adapters, this.defaultProvider);
|
|
11780
13877
|
this.vision = new VisionNamespace(this);
|
|
13878
|
+
this.research = new ResearchNamespace(this.adapters, this.parser);
|
|
11781
13879
|
}
|
|
11782
13880
|
stream(options) {
|
|
11783
13881
|
const descriptor = this.parser.parse(options.model);
|
|
@@ -12044,8 +14142,8 @@ var init_builder = __esm({
|
|
|
12044
14142
|
return this;
|
|
12045
14143
|
}
|
|
12046
14144
|
/** Set maximum iterations. */
|
|
12047
|
-
withMaxIterations(
|
|
12048
|
-
this.core.maxIterations =
|
|
14145
|
+
withMaxIterations(max2) {
|
|
14146
|
+
this.core.maxIterations = max2;
|
|
12049
14147
|
return this;
|
|
12050
14148
|
}
|
|
12051
14149
|
/** Set the budget limit in USD. */
|
|
@@ -12174,10 +14272,10 @@ var init_builder = __esm({
|
|
|
12174
14272
|
return this;
|
|
12175
14273
|
}
|
|
12176
14274
|
/** Set the maximum number of gadgets to execute per LLM response. */
|
|
12177
|
-
withMaxGadgetsPerResponse(
|
|
12178
|
-
if (
|
|
12179
|
-
if (!Number.isInteger(
|
|
12180
|
-
this.gadgets.maxGadgetsPerResponse =
|
|
14275
|
+
withMaxGadgetsPerResponse(max2) {
|
|
14276
|
+
if (max2 < 0) throw new Error("maxGadgetsPerResponse must be a non-negative number");
|
|
14277
|
+
if (!Number.isInteger(max2)) throw new Error("maxGadgetsPerResponse must be an integer");
|
|
14278
|
+
this.gadgets.maxGadgetsPerResponse = max2;
|
|
12181
14279
|
return this;
|
|
12182
14280
|
}
|
|
12183
14281
|
/** Enable or disable gadget output limiting. */
|
|
@@ -12878,6 +14976,8 @@ var init_cost_reporting_client = __esm({
|
|
|
12878
14976
|
}
|
|
12879
14977
|
};
|
|
12880
14978
|
}
|
|
14979
|
+
client;
|
|
14980
|
+
reportCost;
|
|
12881
14981
|
image;
|
|
12882
14982
|
speech;
|
|
12883
14983
|
/**
|
|
@@ -13154,13 +15254,22 @@ var init_parser2 = __esm({
|
|
|
13154
15254
|
GadgetCallParser = class {
|
|
13155
15255
|
buffer = "";
|
|
13156
15256
|
lastEmittedTextOffset = 0;
|
|
15257
|
+
/** Non-null only while a single trailing gadget block is mid-stream. */
|
|
15258
|
+
currentPartial = null;
|
|
13157
15259
|
startPrefix;
|
|
13158
15260
|
endPrefix;
|
|
13159
15261
|
argPrefix;
|
|
15262
|
+
/** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
|
|
15263
|
+
maxMarkerLength;
|
|
13160
15264
|
constructor(options = {}) {
|
|
13161
15265
|
this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
|
|
13162
15266
|
this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
|
|
13163
15267
|
this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
|
|
15268
|
+
this.maxMarkerLength = Math.max(
|
|
15269
|
+
this.startPrefix.length,
|
|
15270
|
+
this.endPrefix.length,
|
|
15271
|
+
this.argPrefix.length
|
|
15272
|
+
);
|
|
13164
15273
|
}
|
|
13165
15274
|
/**
|
|
13166
15275
|
* Extract and consume text up to the given index.
|
|
@@ -13247,12 +15356,13 @@ var init_parser2 = __esm({
|
|
|
13247
15356
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
13248
15357
|
if (metadataEndIndex === -1) break;
|
|
13249
15358
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
13250
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
15359
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
13251
15360
|
const contentStartIndex = metadataEndIndex + 1;
|
|
13252
15361
|
let partEndIndex;
|
|
13253
15362
|
let endMarkerLength = 0;
|
|
13254
|
-
const
|
|
13255
|
-
const
|
|
15363
|
+
const bodySearchStart = this.currentPartial ? contentStartIndex + Math.max(0, this.currentPartial.bodyScannedLen - (this.maxMarkerLength - 1)) : contentStartIndex;
|
|
15364
|
+
const nextStartPos = this.buffer.indexOf(this.startPrefix, bodySearchStart);
|
|
15365
|
+
const endPos = this.buffer.indexOf(this.endPrefix, bodySearchStart);
|
|
13256
15366
|
if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
|
|
13257
15367
|
partEndIndex = nextStartPos;
|
|
13258
15368
|
endMarkerLength = 0;
|
|
@@ -13260,9 +15370,22 @@ var init_parser2 = __esm({
|
|
|
13260
15370
|
partEndIndex = endPos;
|
|
13261
15371
|
endMarkerLength = this.endPrefix.length;
|
|
13262
15372
|
} else {
|
|
15373
|
+
if (!this.currentPartial) {
|
|
15374
|
+
this.currentPartial = this.newPartialState(gadgetName, invocationId, dependencies);
|
|
15375
|
+
}
|
|
15376
|
+
yield* this.emitArgPartials(
|
|
15377
|
+
this.currentPartial,
|
|
15378
|
+
contentStartIndex,
|
|
15379
|
+
this.buffer.length,
|
|
15380
|
+
false
|
|
15381
|
+
);
|
|
13263
15382
|
break;
|
|
13264
15383
|
}
|
|
13265
|
-
const
|
|
15384
|
+
const rawSlice = this.buffer.substring(contentStartIndex, partEndIndex);
|
|
15385
|
+
if (this.currentPartial) {
|
|
15386
|
+
yield* this.emitArgPartials(this.currentPartial, contentStartIndex, partEndIndex, true);
|
|
15387
|
+
}
|
|
15388
|
+
const parametersRaw = rawSlice.trim();
|
|
13266
15389
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
13267
15390
|
yield {
|
|
13268
15391
|
type: "gadget_call",
|
|
@@ -13275,6 +15398,7 @@ var init_parser2 = __esm({
|
|
|
13275
15398
|
dependencies
|
|
13276
15399
|
}
|
|
13277
15400
|
};
|
|
15401
|
+
this.currentPartial = null;
|
|
13278
15402
|
startIndex = partEndIndex + endMarkerLength;
|
|
13279
15403
|
this.lastEmittedTextOffset = startIndex;
|
|
13280
15404
|
}
|
|
@@ -13283,6 +15407,117 @@ var init_parser2 = __esm({
|
|
|
13283
15407
|
this.lastEmittedTextOffset = 0;
|
|
13284
15408
|
}
|
|
13285
15409
|
}
|
|
15410
|
+
/** Create fresh partial-tracking state for a newly-started streaming gadget. */
|
|
15411
|
+
newPartialState(gadgetName, invocationId, dependencies) {
|
|
15412
|
+
return {
|
|
15413
|
+
gadgetName,
|
|
15414
|
+
invocationId,
|
|
15415
|
+
dependencies,
|
|
15416
|
+
emittedFieldLengths: /* @__PURE__ */ new Map(),
|
|
15417
|
+
completedFields: /* @__PURE__ */ new Set(),
|
|
15418
|
+
bodyScannedLen: 0,
|
|
15419
|
+
lastArgRelOffset: -1
|
|
15420
|
+
};
|
|
15421
|
+
}
|
|
15422
|
+
/**
|
|
15423
|
+
* Emit per-field "growing value" partials for an in-progress (or, when
|
|
15424
|
+
* `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd).
|
|
15425
|
+
*
|
|
15426
|
+
* Incremental by design: each call resumes the `!!!ARG:` scan near where the last
|
|
15427
|
+
* one stopped (backing off by one marker's worth so a marker split across a chunk
|
|
15428
|
+
* boundary is still found) and only re-touches the in-progress field, so a long
|
|
15429
|
+
* streamed body costs O(new bytes) per feed instead of O(body). Every field except
|
|
15430
|
+
* the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the
|
|
15431
|
+
* last field is tentative unless `allComplete`. The tentative field holds back any
|
|
15432
|
+
* suffix that is a partial prefix of a gadget marker so it never leaks into a value.
|
|
15433
|
+
*
|
|
15434
|
+
* We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence
|
|
15435
|
+
* sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call
|
|
15436
|
+
* still strips fences from the full raw parameters.
|
|
15437
|
+
*/
|
|
15438
|
+
*emitArgPartials(state, bodyStart, bodyEnd, allComplete) {
|
|
15439
|
+
const argLen = this.argPrefix.length;
|
|
15440
|
+
let searchAbs = bodyStart + Math.max(0, state.bodyScannedLen - (this.maxMarkerLength - 1));
|
|
15441
|
+
if (state.lastArgRelOffset >= 0) {
|
|
15442
|
+
searchAbs = Math.max(searchAbs, bodyStart + state.lastArgRelOffset + argLen);
|
|
15443
|
+
}
|
|
15444
|
+
while (true) {
|
|
15445
|
+
const argAbs = this.buffer.indexOf(this.argPrefix, searchAbs);
|
|
15446
|
+
if (argAbs === -1 || argAbs >= bodyEnd) break;
|
|
15447
|
+
if (state.lastArgRelOffset >= 0) {
|
|
15448
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, argAbs, true);
|
|
15449
|
+
}
|
|
15450
|
+
state.lastArgRelOffset = argAbs - bodyStart;
|
|
15451
|
+
searchAbs = argAbs + argLen;
|
|
15452
|
+
}
|
|
15453
|
+
if (state.lastArgRelOffset >= 0) {
|
|
15454
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, bodyEnd, allComplete);
|
|
15455
|
+
}
|
|
15456
|
+
state.bodyScannedLen = bodyEnd - bodyStart;
|
|
15457
|
+
}
|
|
15458
|
+
/**
|
|
15459
|
+
* Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value
|
|
15460
|
+
* runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field
|
|
15461
|
+
* semantics of the old split-based emitter: field-path line, hold-back for a
|
|
15462
|
+
* tentative value, single trailing-newline strip.
|
|
15463
|
+
*/
|
|
15464
|
+
*emitFieldRange(state, markerAbs, valueEndAbs, complete2) {
|
|
15465
|
+
const pathStart = markerAbs + this.argPrefix.length;
|
|
15466
|
+
const newlineAbs = this.buffer.indexOf("\n", pathStart);
|
|
15467
|
+
if (newlineAbs === -1 || newlineAbs >= valueEndAbs) {
|
|
15468
|
+
if (!complete2) return;
|
|
15469
|
+
const pointer = this.buffer.substring(pathStart, valueEndAbs).trim();
|
|
15470
|
+
if (pointer) yield* this.emitFieldDelta(state, pointer, "", true);
|
|
15471
|
+
return;
|
|
15472
|
+
}
|
|
15473
|
+
const fieldPath = this.buffer.substring(pathStart, newlineAbs).trim();
|
|
15474
|
+
if (!fieldPath) return;
|
|
15475
|
+
let value = this.buffer.substring(newlineAbs + 1, valueEndAbs);
|
|
15476
|
+
if (!complete2) {
|
|
15477
|
+
const hold = this.trailingPartialMarkerLength(value);
|
|
15478
|
+
if (hold > 0) value = value.slice(0, value.length - hold);
|
|
15479
|
+
}
|
|
15480
|
+
if (value.endsWith("\n")) value = value.slice(0, -1);
|
|
15481
|
+
yield* this.emitFieldDelta(state, fieldPath, value, complete2);
|
|
15482
|
+
}
|
|
15483
|
+
/**
|
|
15484
|
+
* Emit a single partial for a field, but only when its value grew or it newly
|
|
15485
|
+
* completed — keeping event volume proportional to field growth, not characters.
|
|
15486
|
+
*/
|
|
15487
|
+
*emitFieldDelta(state, fieldPath, value, fieldComplete) {
|
|
15488
|
+
const previousLength = state.emittedFieldLengths.get(fieldPath) ?? -1;
|
|
15489
|
+
const grew = value.length > previousLength;
|
|
15490
|
+
const newlyComplete = fieldComplete && !state.completedFields.has(fieldPath);
|
|
15491
|
+
if (!grew && !newlyComplete) return;
|
|
15492
|
+
const delta = previousLength < 0 ? value : value.slice(Math.min(previousLength, value.length));
|
|
15493
|
+
state.emittedFieldLengths.set(fieldPath, value.length);
|
|
15494
|
+
if (fieldComplete) state.completedFields.add(fieldPath);
|
|
15495
|
+
yield {
|
|
15496
|
+
type: "gadget_args_partial",
|
|
15497
|
+
invocationId: state.invocationId,
|
|
15498
|
+
gadgetName: state.gadgetName,
|
|
15499
|
+
fieldPath,
|
|
15500
|
+
value,
|
|
15501
|
+
delta,
|
|
15502
|
+
isFieldComplete: fieldComplete
|
|
15503
|
+
};
|
|
15504
|
+
}
|
|
15505
|
+
/**
|
|
15506
|
+
* Length of the longest suffix of `value` that is a proper prefix of any gadget
|
|
15507
|
+
* marker (start/end/arg). Used to hold back the beginning of an incoming marker
|
|
15508
|
+
* so it never appears inside a streamed value.
|
|
15509
|
+
*/
|
|
15510
|
+
trailingPartialMarkerLength(value) {
|
|
15511
|
+
const markers = [this.startPrefix, this.endPrefix, this.argPrefix];
|
|
15512
|
+
const limit = Math.min(this.maxMarkerLength - 1, value.length);
|
|
15513
|
+
for (let len = limit; len >= 1; len--) {
|
|
15514
|
+
const suffix = value.slice(value.length - len);
|
|
15515
|
+
for (const marker of markers) {
|
|
15516
|
+
if (suffix.length < marker.length && marker.startsWith(suffix)) return len;
|
|
15517
|
+
}
|
|
15518
|
+
}
|
|
15519
|
+
return 0;
|
|
15520
|
+
}
|
|
13286
15521
|
// Finalize parsing and return remaining text or incomplete gadgets
|
|
13287
15522
|
*finalize() {
|
|
13288
15523
|
const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
|
|
@@ -13295,9 +15530,18 @@ var init_parser2 = __esm({
|
|
|
13295
15530
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
13296
15531
|
if (metadataEndIndex !== -1) {
|
|
13297
15532
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
13298
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
15533
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
13299
15534
|
const contentStartIndex = metadataEndIndex + 1;
|
|
13300
|
-
const
|
|
15535
|
+
const contentRaw = this.buffer.substring(contentStartIndex);
|
|
15536
|
+
if (this.currentPartial) {
|
|
15537
|
+
yield* this.emitArgPartials(
|
|
15538
|
+
this.currentPartial,
|
|
15539
|
+
contentStartIndex,
|
|
15540
|
+
this.buffer.length,
|
|
15541
|
+
true
|
|
15542
|
+
);
|
|
15543
|
+
}
|
|
15544
|
+
const parametersRaw = contentRaw.trim();
|
|
13301
15545
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
13302
15546
|
yield {
|
|
13303
15547
|
type: "gadget_call",
|
|
@@ -13310,6 +15554,7 @@ var init_parser2 = __esm({
|
|
|
13310
15554
|
dependencies
|
|
13311
15555
|
}
|
|
13312
15556
|
};
|
|
15557
|
+
this.currentPartial = null;
|
|
13313
15558
|
return;
|
|
13314
15559
|
}
|
|
13315
15560
|
}
|
|
@@ -13322,6 +15567,7 @@ var init_parser2 = __esm({
|
|
|
13322
15567
|
reset() {
|
|
13323
15568
|
this.buffer = "";
|
|
13324
15569
|
this.lastEmittedTextOffset = 0;
|
|
15570
|
+
this.currentPartial = null;
|
|
13325
15571
|
}
|
|
13326
15572
|
};
|
|
13327
15573
|
}
|
|
@@ -14247,6 +16493,29 @@ async function notifyGadgetStart(ctx) {
|
|
|
14247
16493
|
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14248
16494
|
}
|
|
14249
16495
|
}
|
|
16496
|
+
async function notifyGadgetArgsPartial(ctx) {
|
|
16497
|
+
if (!ctx.hooks?.onGadgetArgsPartial && !ctx.parentObservers?.onGadgetArgsPartial) return;
|
|
16498
|
+
const subagentContext = ctx.tree && ctx.parentNodeId ? getSubagentContextForNode(ctx.tree, ctx.parentNodeId) : void 0;
|
|
16499
|
+
const context = {
|
|
16500
|
+
iteration: ctx.iteration,
|
|
16501
|
+
invocationId: ctx.event.invocationId,
|
|
16502
|
+
gadgetName: ctx.event.gadgetName,
|
|
16503
|
+
fieldPath: ctx.event.fieldPath,
|
|
16504
|
+
value: ctx.event.value,
|
|
16505
|
+
delta: ctx.event.delta,
|
|
16506
|
+
isFieldComplete: ctx.event.isFieldComplete,
|
|
16507
|
+
logger: ctx.logger,
|
|
16508
|
+
subagentContext
|
|
16509
|
+
};
|
|
16510
|
+
if (ctx.hooks?.onGadgetArgsPartial) {
|
|
16511
|
+
const hookFn = ctx.hooks.onGadgetArgsPartial;
|
|
16512
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
16513
|
+
}
|
|
16514
|
+
if (ctx.parentObservers?.onGadgetArgsPartial) {
|
|
16515
|
+
const hookFn = ctx.parentObservers.onGadgetArgsPartial;
|
|
16516
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
16517
|
+
}
|
|
16518
|
+
}
|
|
14250
16519
|
async function notifyGadgetComplete(ctx) {
|
|
14251
16520
|
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
14252
16521
|
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
@@ -15055,6 +17324,7 @@ var init_stream_processor = __esm({
|
|
|
15055
17324
|
init_gadget_dispatcher();
|
|
15056
17325
|
init_gadget_hook_lifecycle();
|
|
15057
17326
|
init_gadget_limit_guard();
|
|
17327
|
+
init_observer_notifier();
|
|
15058
17328
|
init_safe_observe();
|
|
15059
17329
|
StreamProcessor = class {
|
|
15060
17330
|
iteration;
|
|
@@ -15063,6 +17333,10 @@ var init_stream_processor = __esm({
|
|
|
15063
17333
|
parser;
|
|
15064
17334
|
// Execution Tree context
|
|
15065
17335
|
tree;
|
|
17336
|
+
/** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */
|
|
17337
|
+
parentNodeId;
|
|
17338
|
+
/** Parent agent observers (subagent visibility) — also notified for arg partials. */
|
|
17339
|
+
parentObservers;
|
|
15066
17340
|
responseText = "";
|
|
15067
17341
|
// Dependency resolution is delegated to GadgetDependencyResolver
|
|
15068
17342
|
dependencyResolver;
|
|
@@ -15076,6 +17350,8 @@ var init_stream_processor = __esm({
|
|
|
15076
17350
|
this.hooks = options.hooks ?? {};
|
|
15077
17351
|
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
15078
17352
|
this.tree = options.tree;
|
|
17353
|
+
this.parentNodeId = options.parentNodeId;
|
|
17354
|
+
this.parentObservers = options.parentObservers;
|
|
15079
17355
|
this.dependencyResolver = new GadgetDependencyResolver({
|
|
15080
17356
|
priorCompletedInvocations: options.priorCompletedInvocations,
|
|
15081
17357
|
priorFailedInvocations: options.priorFailedInvocations
|
|
@@ -15277,6 +17553,17 @@ var init_stream_processor = __esm({
|
|
|
15277
17553
|
for await (const e of this.dispatcher.dispatch(event.call)) {
|
|
15278
17554
|
yield e;
|
|
15279
17555
|
}
|
|
17556
|
+
} else if (event.type === "gadget_args_partial") {
|
|
17557
|
+
await notifyGadgetArgsPartial({
|
|
17558
|
+
tree: this.tree,
|
|
17559
|
+
hooks: this.hooks.observers,
|
|
17560
|
+
parentObservers: this.parentObservers,
|
|
17561
|
+
logger: this.logger,
|
|
17562
|
+
iteration: this.iteration,
|
|
17563
|
+
parentNodeId: this.parentNodeId,
|
|
17564
|
+
event
|
|
17565
|
+
});
|
|
17566
|
+
yield event;
|
|
15280
17567
|
} else {
|
|
15281
17568
|
yield event;
|
|
15282
17569
|
}
|
|
@@ -15847,7 +18134,7 @@ var init_agent = __esm({
|
|
|
15847
18134
|
}
|
|
15848
18135
|
const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
|
|
15849
18136
|
if (this.mcpSpecs.length > 0) {
|
|
15850
|
-
const { setupMcpServers } = await import("./runtime-
|
|
18137
|
+
const { setupMcpServers } = await import("./runtime-IUKW77QX.js");
|
|
15851
18138
|
this.mcpLifecycle = await setupMcpServers({
|
|
15852
18139
|
specs: this.mcpSpecs,
|
|
15853
18140
|
registry: this.registry,
|
|
@@ -16672,6 +18959,14 @@ init_tool_adapter();
|
|
|
16672
18959
|
// src/index.ts
|
|
16673
18960
|
init_constants2();
|
|
16674
18961
|
|
|
18962
|
+
// src/research/index.ts
|
|
18963
|
+
init_collector();
|
|
18964
|
+
init_constants3();
|
|
18965
|
+
init_cost();
|
|
18966
|
+
init_errors3();
|
|
18967
|
+
init_namespace();
|
|
18968
|
+
init_types();
|
|
18969
|
+
|
|
16675
18970
|
// src/utils/config-resolver.ts
|
|
16676
18971
|
function resolveValue(ctx, gadgetName, options) {
|
|
16677
18972
|
const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
|
|
@@ -16970,11 +19265,11 @@ var format = {
|
|
|
16970
19265
|
};
|
|
16971
19266
|
|
|
16972
19267
|
// src/utils/timing.ts
|
|
16973
|
-
function randomDelay(min,
|
|
16974
|
-
return Math.floor(Math.random() * (
|
|
19268
|
+
function randomDelay(min, max2) {
|
|
19269
|
+
return Math.floor(Math.random() * (max2 - min + 1)) + min;
|
|
16975
19270
|
}
|
|
16976
|
-
async function humanDelay(min = 50,
|
|
16977
|
-
const delay = randomDelay(min,
|
|
19271
|
+
async function humanDelay(min = 50, max2 = 150) {
|
|
19272
|
+
const delay = randomDelay(min, max2);
|
|
16978
19273
|
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
16979
19274
|
}
|
|
16980
19275
|
async function withTimeout(fn, timeoutMs, signal) {
|
|
@@ -17113,7 +19408,17 @@ export {
|
|
|
17113
19408
|
OpenAIChatProvider,
|
|
17114
19409
|
OpenAICompatibleProvider,
|
|
17115
19410
|
OpenRouterProvider,
|
|
19411
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
17116
19412
|
RateLimitTracker,
|
|
19413
|
+
ResearchDeprecatedModelError,
|
|
19414
|
+
ResearchJobNotResumableError,
|
|
19415
|
+
ResearchNamespace,
|
|
19416
|
+
ResearchNotPollableError,
|
|
19417
|
+
ResearchNotSupportedError,
|
|
19418
|
+
ResearchResultCollector,
|
|
19419
|
+
ResearchStreamConsumedError,
|
|
19420
|
+
ResearchTimeoutError,
|
|
19421
|
+
ResearchValidationError,
|
|
17117
19422
|
SimpleSessionManager,
|
|
17118
19423
|
Skill,
|
|
17119
19424
|
SkillRegistry,
|
|
@@ -17147,6 +19452,7 @@ export {
|
|
|
17147
19452
|
detectImageMimeType,
|
|
17148
19453
|
discoverProviderAdapters,
|
|
17149
19454
|
discoverSkills,
|
|
19455
|
+
estimateResearchCost,
|
|
17150
19456
|
extractMessageText,
|
|
17151
19457
|
extractRetryAfterMs,
|
|
17152
19458
|
filterByDepth,
|