llmist 18.3.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 +2201 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +592 -1
- package/dist/index.d.ts +592 -1
- package/dist/index.js +2145 -38
- 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
|
// ===========================================================================
|
|
@@ -3636,7 +3638,7 @@ var init_gadget_output_store = __esm({
|
|
|
3636
3638
|
charCount: content.length,
|
|
3637
3639
|
byteSize: encoder.encode(content).length,
|
|
3638
3640
|
lineCount: lines.length,
|
|
3639
|
-
maxLineLength: lines.reduce((
|
|
3641
|
+
maxLineLength: lines.reduce((max2, line) => Math.max(max2, line.length), 0),
|
|
3640
3642
|
timestamp: /* @__PURE__ */ new Date()
|
|
3641
3643
|
};
|
|
3642
3644
|
this.outputs.set(id, stored);
|
|
@@ -6286,6 +6288,7 @@ var init_base_provider = __esm({
|
|
|
6286
6288
|
constructor(client) {
|
|
6287
6289
|
this.client = client;
|
|
6288
6290
|
}
|
|
6291
|
+
client;
|
|
6289
6292
|
/**
|
|
6290
6293
|
* Template method that defines the skeleton of the streaming algorithm.
|
|
6291
6294
|
* This orchestrates the four-step process without dictating provider-specific details.
|
|
@@ -6655,6 +6658,7 @@ var init_gemini_cache_manager = __esm({
|
|
|
6655
6658
|
constructor(client) {
|
|
6656
6659
|
this.client = client;
|
|
6657
6660
|
}
|
|
6661
|
+
client;
|
|
6658
6662
|
activeCache = null;
|
|
6659
6663
|
/**
|
|
6660
6664
|
* Get or create a cache for the given content.
|
|
@@ -7197,6 +7201,621 @@ var init_gemini_models = __esm({
|
|
|
7197
7201
|
}
|
|
7198
7202
|
});
|
|
7199
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
|
+
|
|
7200
7819
|
// src/providers/gemini-speech-models.ts
|
|
7201
7820
|
function getGeminiSpeechModelSpec(modelId) {
|
|
7202
7821
|
return geminiSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -7394,6 +8013,8 @@ var init_gemini = __esm({
|
|
|
7394
8013
|
init_gemini_cache_manager();
|
|
7395
8014
|
init_gemini_image_models();
|
|
7396
8015
|
init_gemini_models();
|
|
8016
|
+
init_gemini_research();
|
|
8017
|
+
init_gemini_research_models();
|
|
7397
8018
|
init_gemini_speech_models();
|
|
7398
8019
|
init_utils();
|
|
7399
8020
|
GEMINI3_PRO_THINKING_LEVEL = {
|
|
@@ -7613,6 +8234,27 @@ var init_gemini = __esm({
|
|
|
7613
8234
|
format: spec?.defaultFormat ?? "wav"
|
|
7614
8235
|
};
|
|
7615
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
|
+
}
|
|
7616
8258
|
buildApiRequest(options, descriptor, _spec, messages) {
|
|
7617
8259
|
const contents = this.convertMessagesToContents(messages);
|
|
7618
8260
|
return this.buildApiRequestFromContents(options, descriptor, _spec, contents, null, 0);
|
|
@@ -8839,17 +9481,17 @@ var init_openai_compatible_provider = __esm({
|
|
|
8839
9481
|
async executeStreamRequest(payload, signal) {
|
|
8840
9482
|
const client = this.client;
|
|
8841
9483
|
const headers = this.getCustomHeaders();
|
|
8842
|
-
const
|
|
9484
|
+
const requestOptions3 = {};
|
|
8843
9485
|
if (signal) {
|
|
8844
|
-
|
|
9486
|
+
requestOptions3.signal = signal;
|
|
8845
9487
|
}
|
|
8846
9488
|
if (Object.keys(headers).length > 0) {
|
|
8847
|
-
|
|
9489
|
+
requestOptions3.headers = headers;
|
|
8848
9490
|
}
|
|
8849
9491
|
try {
|
|
8850
9492
|
const stream2 = await client.chat.completions.create(
|
|
8851
9493
|
payload,
|
|
8852
|
-
Object.keys(
|
|
9494
|
+
Object.keys(requestOptions3).length > 0 ? requestOptions3 : void 0
|
|
8853
9495
|
);
|
|
8854
9496
|
return stream2;
|
|
8855
9497
|
} catch (error) {
|
|
@@ -9726,6 +10368,549 @@ var init_openai_models = __esm({
|
|
|
9726
10368
|
}
|
|
9727
10369
|
});
|
|
9728
10370
|
|
|
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;
|
|
10401
|
+
}
|
|
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;
|
|
10431
|
+
}
|
|
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";
|
|
10442
|
+
}
|
|
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
|
+
}
|
|
10457
|
+
}
|
|
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;
|
|
10475
|
+
}
|
|
10476
|
+
return searches;
|
|
10477
|
+
}
|
|
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
|
+
|
|
9729
10914
|
// src/providers/openai-speech-models.ts
|
|
9730
10915
|
function getOpenAISpeechModelSpec(modelId) {
|
|
9731
10916
|
return openaiSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -9881,6 +11066,8 @@ var init_openai = __esm({
|
|
|
9881
11066
|
init_constants2();
|
|
9882
11067
|
init_openai_image_models();
|
|
9883
11068
|
init_openai_models();
|
|
11069
|
+
init_openai_research();
|
|
11070
|
+
init_openai_research_models();
|
|
9884
11071
|
init_openai_speech_models();
|
|
9885
11072
|
init_utils();
|
|
9886
11073
|
ROLE_MAP2 = {
|
|
@@ -9984,6 +11171,32 @@ var init_openai = __esm({
|
|
|
9984
11171
|
format: format2
|
|
9985
11172
|
};
|
|
9986
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
|
+
}
|
|
9987
11200
|
buildApiRequest(options, descriptor, spec, messages) {
|
|
9988
11201
|
const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
|
|
9989
11202
|
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
@@ -10679,35 +11892,246 @@ var init_openrouter_models = __esm({
|
|
|
10679
11892
|
vision: false
|
|
10680
11893
|
},
|
|
10681
11894
|
metadata: {
|
|
10682
|
-
family: "Mixtral",
|
|
10683
|
-
notes: "Mixtral 8x22B via OpenRouter. Sparse MoE architecture."
|
|
11895
|
+
family: "Mixtral",
|
|
11896
|
+
notes: "Mixtral 8x22B via OpenRouter. Sparse MoE architecture."
|
|
11897
|
+
}
|
|
11898
|
+
},
|
|
11899
|
+
// ============================================================
|
|
11900
|
+
// Qwen Models (via OpenRouter)
|
|
11901
|
+
// ============================================================
|
|
11902
|
+
{
|
|
11903
|
+
provider: "openrouter",
|
|
11904
|
+
modelId: "qwen/qwen-2.5-72b-instruct",
|
|
11905
|
+
displayName: "Qwen 2.5 72B Instruct (OpenRouter)",
|
|
11906
|
+
contextWindow: 131072,
|
|
11907
|
+
maxOutputTokens: 8192,
|
|
11908
|
+
pricing: {
|
|
11909
|
+
input: 0.35,
|
|
11910
|
+
output: 0.4
|
|
11911
|
+
},
|
|
11912
|
+
knowledgeCutoff: "2024-09",
|
|
11913
|
+
features: {
|
|
11914
|
+
streaming: true,
|
|
11915
|
+
functionCalling: true,
|
|
11916
|
+
vision: false
|
|
11917
|
+
},
|
|
11918
|
+
metadata: {
|
|
11919
|
+
family: "Qwen 2.5",
|
|
11920
|
+
notes: "Qwen 2.5 72B via OpenRouter. Strong coding and math."
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
];
|
|
11924
|
+
}
|
|
11925
|
+
});
|
|
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."
|
|
10684
12088
|
}
|
|
10685
12089
|
},
|
|
10686
|
-
// ============================================================
|
|
10687
|
-
// Qwen Models (via OpenRouter)
|
|
10688
|
-
// ============================================================
|
|
10689
12090
|
{
|
|
10690
12091
|
provider: "openrouter",
|
|
10691
|
-
modelId: "
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
pricing: {
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
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,
|
|
10705
12109
|
metadata: {
|
|
10706
|
-
|
|
10707
|
-
|
|
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."
|
|
10708
12131
|
}
|
|
10709
12132
|
}
|
|
10710
12133
|
];
|
|
12134
|
+
byId3 = new Map(openrouterResearchModels.map((spec) => [spec.modelId, spec]));
|
|
10711
12135
|
}
|
|
10712
12136
|
});
|
|
10713
12137
|
|
|
@@ -10838,8 +12262,11 @@ var init_openrouter = __esm({
|
|
|
10838
12262
|
"use strict";
|
|
10839
12263
|
init_model_shortcuts();
|
|
10840
12264
|
init_logger();
|
|
12265
|
+
init_constants3();
|
|
10841
12266
|
init_openai_compatible_provider();
|
|
10842
12267
|
init_openrouter_models();
|
|
12268
|
+
init_openrouter_research();
|
|
12269
|
+
init_openrouter_research_models();
|
|
10843
12270
|
init_openrouter_speech_models();
|
|
10844
12271
|
init_utils();
|
|
10845
12272
|
logger = createLogger({ name: "openrouter" });
|
|
@@ -10859,6 +12286,66 @@ var init_openrouter = __esm({
|
|
|
10859
12286
|
getModelSpecs() {
|
|
10860
12287
|
return OPENROUTER_MODELS;
|
|
10861
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
|
+
}
|
|
10862
12349
|
/**
|
|
10863
12350
|
* Override buildApiRequest to inject reasoning parameters and cache_control breakpoints.
|
|
10864
12351
|
* OpenRouter normalizes reasoning into the standard OpenAI format,
|
|
@@ -11158,6 +12645,590 @@ var init_discovery = __esm({
|
|
|
11158
12645
|
}
|
|
11159
12646
|
});
|
|
11160
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
|
+
|
|
11161
13232
|
// src/core/model-registry.ts
|
|
11162
13233
|
var ModelRegistry;
|
|
11163
13234
|
var init_model_registry = __esm({
|
|
@@ -11382,6 +13453,8 @@ var init_image = __esm({
|
|
|
11382
13453
|
this.adapters = adapters;
|
|
11383
13454
|
this.defaultProvider = defaultProvider;
|
|
11384
13455
|
}
|
|
13456
|
+
adapters;
|
|
13457
|
+
defaultProvider;
|
|
11385
13458
|
/**
|
|
11386
13459
|
* Generate images from a text prompt.
|
|
11387
13460
|
*
|
|
@@ -11434,6 +13507,8 @@ var init_speech = __esm({
|
|
|
11434
13507
|
this.adapters = adapters;
|
|
11435
13508
|
this.defaultProvider = defaultProvider;
|
|
11436
13509
|
}
|
|
13510
|
+
adapters;
|
|
13511
|
+
defaultProvider;
|
|
11437
13512
|
/**
|
|
11438
13513
|
* Generate speech audio from text.
|
|
11439
13514
|
*
|
|
@@ -11529,6 +13604,7 @@ var init_text = __esm({
|
|
|
11529
13604
|
constructor(client) {
|
|
11530
13605
|
this.client = client;
|
|
11531
13606
|
}
|
|
13607
|
+
client;
|
|
11532
13608
|
/**
|
|
11533
13609
|
* Generate a complete text response.
|
|
11534
13610
|
*
|
|
@@ -11564,6 +13640,7 @@ var init_vision = __esm({
|
|
|
11564
13640
|
constructor(client) {
|
|
11565
13641
|
this.client = client;
|
|
11566
13642
|
}
|
|
13643
|
+
client;
|
|
11567
13644
|
/**
|
|
11568
13645
|
* Build a message builder with the image content attached.
|
|
11569
13646
|
* Handles URLs, data URLs, base64 strings, and binary buffers.
|
|
@@ -11690,6 +13767,7 @@ var init_options = __esm({
|
|
|
11690
13767
|
constructor(defaultProvider = "openai") {
|
|
11691
13768
|
this.defaultProvider = defaultProvider;
|
|
11692
13769
|
}
|
|
13770
|
+
defaultProvider;
|
|
11693
13771
|
parse(identifier) {
|
|
11694
13772
|
const trimmed = identifier.trim();
|
|
11695
13773
|
if (!trimmed) {
|
|
@@ -11721,6 +13799,7 @@ var init_client2 = __esm({
|
|
|
11721
13799
|
"use strict";
|
|
11722
13800
|
init_builder();
|
|
11723
13801
|
init_discovery();
|
|
13802
|
+
init_namespace();
|
|
11724
13803
|
init_constants();
|
|
11725
13804
|
init_model_registry();
|
|
11726
13805
|
init_image();
|
|
@@ -11739,6 +13818,12 @@ var init_client2 = __esm({
|
|
|
11739
13818
|
image;
|
|
11740
13819
|
speech;
|
|
11741
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;
|
|
11742
13827
|
constructor(...args) {
|
|
11743
13828
|
let adapters = [];
|
|
11744
13829
|
let defaultProvider;
|
|
@@ -11790,6 +13875,7 @@ var init_client2 = __esm({
|
|
|
11790
13875
|
this.image = new ImageNamespace(this.adapters, this.defaultProvider);
|
|
11791
13876
|
this.speech = new SpeechNamespace(this.adapters, this.defaultProvider);
|
|
11792
13877
|
this.vision = new VisionNamespace(this);
|
|
13878
|
+
this.research = new ResearchNamespace(this.adapters, this.parser);
|
|
11793
13879
|
}
|
|
11794
13880
|
stream(options) {
|
|
11795
13881
|
const descriptor = this.parser.parse(options.model);
|
|
@@ -12056,8 +14142,8 @@ var init_builder = __esm({
|
|
|
12056
14142
|
return this;
|
|
12057
14143
|
}
|
|
12058
14144
|
/** Set maximum iterations. */
|
|
12059
|
-
withMaxIterations(
|
|
12060
|
-
this.core.maxIterations =
|
|
14145
|
+
withMaxIterations(max2) {
|
|
14146
|
+
this.core.maxIterations = max2;
|
|
12061
14147
|
return this;
|
|
12062
14148
|
}
|
|
12063
14149
|
/** Set the budget limit in USD. */
|
|
@@ -12186,10 +14272,10 @@ var init_builder = __esm({
|
|
|
12186
14272
|
return this;
|
|
12187
14273
|
}
|
|
12188
14274
|
/** Set the maximum number of gadgets to execute per LLM response. */
|
|
12189
|
-
withMaxGadgetsPerResponse(
|
|
12190
|
-
if (
|
|
12191
|
-
if (!Number.isInteger(
|
|
12192
|
-
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;
|
|
12193
14279
|
return this;
|
|
12194
14280
|
}
|
|
12195
14281
|
/** Enable or disable gadget output limiting. */
|
|
@@ -12890,6 +14976,8 @@ var init_cost_reporting_client = __esm({
|
|
|
12890
14976
|
}
|
|
12891
14977
|
};
|
|
12892
14978
|
}
|
|
14979
|
+
client;
|
|
14980
|
+
reportCost;
|
|
12893
14981
|
image;
|
|
12894
14982
|
speech;
|
|
12895
14983
|
/**
|
|
@@ -16046,7 +18134,7 @@ var init_agent = __esm({
|
|
|
16046
18134
|
}
|
|
16047
18135
|
const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
|
|
16048
18136
|
if (this.mcpSpecs.length > 0) {
|
|
16049
|
-
const { setupMcpServers } = await import("./runtime-
|
|
18137
|
+
const { setupMcpServers } = await import("./runtime-IUKW77QX.js");
|
|
16050
18138
|
this.mcpLifecycle = await setupMcpServers({
|
|
16051
18139
|
specs: this.mcpSpecs,
|
|
16052
18140
|
registry: this.registry,
|
|
@@ -16871,6 +18959,14 @@ init_tool_adapter();
|
|
|
16871
18959
|
// src/index.ts
|
|
16872
18960
|
init_constants2();
|
|
16873
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
|
+
|
|
16874
18970
|
// src/utils/config-resolver.ts
|
|
16875
18971
|
function resolveValue(ctx, gadgetName, options) {
|
|
16876
18972
|
const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
|
|
@@ -17169,11 +19265,11 @@ var format = {
|
|
|
17169
19265
|
};
|
|
17170
19266
|
|
|
17171
19267
|
// src/utils/timing.ts
|
|
17172
|
-
function randomDelay(min,
|
|
17173
|
-
return Math.floor(Math.random() * (
|
|
19268
|
+
function randomDelay(min, max2) {
|
|
19269
|
+
return Math.floor(Math.random() * (max2 - min + 1)) + min;
|
|
17174
19270
|
}
|
|
17175
|
-
async function humanDelay(min = 50,
|
|
17176
|
-
const delay = randomDelay(min,
|
|
19271
|
+
async function humanDelay(min = 50, max2 = 150) {
|
|
19272
|
+
const delay = randomDelay(min, max2);
|
|
17177
19273
|
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
17178
19274
|
}
|
|
17179
19275
|
async function withTimeout(fn, timeoutMs, signal) {
|
|
@@ -17312,7 +19408,17 @@ export {
|
|
|
17312
19408
|
OpenAIChatProvider,
|
|
17313
19409
|
OpenAICompatibleProvider,
|
|
17314
19410
|
OpenRouterProvider,
|
|
19411
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
17315
19412
|
RateLimitTracker,
|
|
19413
|
+
ResearchDeprecatedModelError,
|
|
19414
|
+
ResearchJobNotResumableError,
|
|
19415
|
+
ResearchNamespace,
|
|
19416
|
+
ResearchNotPollableError,
|
|
19417
|
+
ResearchNotSupportedError,
|
|
19418
|
+
ResearchResultCollector,
|
|
19419
|
+
ResearchStreamConsumedError,
|
|
19420
|
+
ResearchTimeoutError,
|
|
19421
|
+
ResearchValidationError,
|
|
17316
19422
|
SimpleSessionManager,
|
|
17317
19423
|
Skill,
|
|
17318
19424
|
SkillRegistry,
|
|
@@ -17346,6 +19452,7 @@ export {
|
|
|
17346
19452
|
detectImageMimeType,
|
|
17347
19453
|
discoverProviderAdapters,
|
|
17348
19454
|
discoverSkills,
|
|
19455
|
+
estimateResearchCost,
|
|
17349
19456
|
extractMessageText,
|
|
17350
19457
|
extractRetryAfterMs,
|
|
17351
19458
|
filterByDepth,
|