llmist 18.2.0 → 18.4.0

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