llmist 18.3.0 → 18.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
  // ===========================================================================
@@ -3636,7 +3638,7 @@ var init_gadget_output_store = __esm({
3636
3638
  charCount: content.length,
3637
3639
  byteSize: encoder.encode(content).length,
3638
3640
  lineCount: lines.length,
3639
- maxLineLength: lines.reduce((max, line) => Math.max(max, line.length), 0),
3641
+ maxLineLength: lines.reduce((max2, line) => Math.max(max2, line.length), 0),
3640
3642
  timestamp: /* @__PURE__ */ new Date()
3641
3643
  };
3642
3644
  this.outputs.set(id, stored);
@@ -6286,6 +6288,7 @@ var init_base_provider = __esm({
6286
6288
  constructor(client) {
6287
6289
  this.client = client;
6288
6290
  }
6291
+ client;
6289
6292
  /**
6290
6293
  * Template method that defines the skeleton of the streaming algorithm.
6291
6294
  * This orchestrates the four-step process without dictating provider-specific details.
@@ -6655,6 +6658,7 @@ var init_gemini_cache_manager = __esm({
6655
6658
  constructor(client) {
6656
6659
  this.client = client;
6657
6660
  }
6661
+ client;
6658
6662
  activeCache = null;
6659
6663
  /**
6660
6664
  * Get or create a cache for the given content.
@@ -7197,6 +7201,621 @@ var init_gemini_models = __esm({
7197
7201
  }
7198
7202
  });
7199
7203
 
7204
+ // src/research/constants.ts
7205
+ var RESEARCH_DEFAULT_TIMEOUT_MS, GEMINI_RESEARCH_MAX_DURATION_MS, OPENAI_RESEARCH_HTTP_TIMEOUT_MS, OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS, RESEARCH_POLL_INTERVAL_MS, RESEARCH_POLL_MAX_INTERVAL_MS, RESEARCH_POLL_BACKOFF_FACTOR, RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS, RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS, RESEARCH_COST_DECIMALS, TOKENS_PER_MILLION, SEARCHES_PER_THOUSAND, MS_PER_DAY;
7206
+ var init_constants3 = __esm({
7207
+ "src/research/constants.ts"() {
7208
+ "use strict";
7209
+ RESEARCH_DEFAULT_TIMEOUT_MS = 36e5;
7210
+ GEMINI_RESEARCH_MAX_DURATION_MS = 36e5;
7211
+ OPENAI_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
7212
+ OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
7213
+ RESEARCH_POLL_INTERVAL_MS = 1e4;
7214
+ RESEARCH_POLL_MAX_INTERVAL_MS = 6e4;
7215
+ RESEARCH_POLL_BACKOFF_FACTOR = 1.5;
7216
+ RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS = 5;
7217
+ RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS = 30;
7218
+ RESEARCH_COST_DECIMALS = 6;
7219
+ TOKENS_PER_MILLION = 1e6;
7220
+ SEARCHES_PER_THOUSAND = 1e3;
7221
+ MS_PER_DAY = 864e5;
7222
+ }
7223
+ });
7224
+
7225
+ // src/research/errors.ts
7226
+ var ResearchNotSupportedError, ResearchJobNotResumableError, ResearchNotPollableError, ResearchTimeoutError, ResearchDeprecatedModelError, ResearchValidationError, ResearchStreamConsumedError;
7227
+ var init_errors3 = __esm({
7228
+ "src/research/errors.ts"() {
7229
+ "use strict";
7230
+ ResearchNotSupportedError = class extends Error {
7231
+ constructor(message) {
7232
+ super(message);
7233
+ this.name = "ResearchNotSupportedError";
7234
+ }
7235
+ };
7236
+ ResearchJobNotResumableError = class extends Error {
7237
+ constructor(message) {
7238
+ super(message);
7239
+ this.name = "ResearchJobNotResumableError";
7240
+ }
7241
+ };
7242
+ ResearchNotPollableError = class extends Error {
7243
+ constructor(message) {
7244
+ super(message);
7245
+ this.name = "ResearchNotPollableError";
7246
+ }
7247
+ };
7248
+ ResearchTimeoutError = class extends Error {
7249
+ timeoutMs;
7250
+ constructor(timeoutMs) {
7251
+ super(
7252
+ `Research run exceeded the client-side time budget of ${timeoutMs}ms. A background job keeps running server-side \u2014 re-attach via its ref.`
7253
+ );
7254
+ this.name = "ResearchTimeoutError";
7255
+ this.timeoutMs = timeoutMs;
7256
+ }
7257
+ };
7258
+ ResearchDeprecatedModelError = class extends Error {
7259
+ modelId;
7260
+ shutdownDate;
7261
+ replacement;
7262
+ constructor(params) {
7263
+ super(
7264
+ `Research model "${params.modelId}" was shut down by its provider on ${params.shutdownDate}.` + (params.replacement ? ` Use "${params.replacement}" instead.` : "")
7265
+ );
7266
+ this.name = "ResearchDeprecatedModelError";
7267
+ this.modelId = params.modelId;
7268
+ this.shutdownDate = params.shutdownDate;
7269
+ this.replacement = params.replacement;
7270
+ }
7271
+ };
7272
+ ResearchValidationError = class extends Error {
7273
+ constructor(message) {
7274
+ super(message);
7275
+ this.name = "ResearchValidationError";
7276
+ }
7277
+ };
7278
+ ResearchStreamConsumedError = class extends Error {
7279
+ constructor() {
7280
+ super(
7281
+ "This research job's event stream was already consumed. Iterate events() (or the job itself) once; use result() for the aggregated outcome."
7282
+ );
7283
+ this.name = "ResearchStreamConsumedError";
7284
+ }
7285
+ };
7286
+ }
7287
+ });
7288
+
7289
+ // src/providers/gemini-research.ts
7290
+ function buildGeminiResearchRequest(options, agentId) {
7291
+ if (options.background === false) {
7292
+ throw new ResearchValidationError(
7293
+ "Gemini deep research requires background execution \u2014 background: false is not supported."
7294
+ );
7295
+ }
7296
+ return {
7297
+ agent: agentId,
7298
+ input: options.query,
7299
+ background: true,
7300
+ store: true,
7301
+ agent_config: {
7302
+ type: "deep-research",
7303
+ thinking_summaries: options.reasoning?.includeThinking === false ? "none" : "auto"
7304
+ },
7305
+ ...options.systemPrompt ? { system_instruction: options.systemPrompt } : {},
7306
+ ...options.previousJobId ? { previous_interaction_id: options.previousJobId } : {},
7307
+ ...options.extra ?? {}
7308
+ };
7309
+ }
7310
+ function mapInteractionStatus(status) {
7311
+ if (status && GEMINI_STATUS_VALUES.has(status)) {
7312
+ return status;
7313
+ }
7314
+ return "in_progress";
7315
+ }
7316
+ function annotationToCitation(annotation) {
7317
+ if (annotation.type !== "url_citation" || !annotation.url) return void 0;
7318
+ return {
7319
+ url: annotation.url,
7320
+ title: annotation.title,
7321
+ startIndex: annotation.start_index,
7322
+ endIndex: annotation.end_index
7323
+ };
7324
+ }
7325
+ function extractReportFromInteraction(interaction) {
7326
+ const parts = [];
7327
+ const citations = [];
7328
+ for (const step of interaction.steps ?? []) {
7329
+ if (step.type !== "model_output") continue;
7330
+ for (const content of step.content ?? []) {
7331
+ if (content.type !== "text") continue;
7332
+ parts.push(content.text);
7333
+ for (const annotation of content.annotations ?? []) {
7334
+ const citation = annotationToCitation(annotation);
7335
+ if (citation) citations.push(citation);
7336
+ }
7337
+ }
7338
+ }
7339
+ return { report: parts.join(""), citations };
7340
+ }
7341
+ function countSearchesInSteps(interaction) {
7342
+ let searches = 0;
7343
+ for (const step of interaction.steps ?? []) {
7344
+ if (step.type === "google_search_call") {
7345
+ searches += step.arguments?.queries?.length ?? 1;
7346
+ }
7347
+ }
7348
+ return searches;
7349
+ }
7350
+ function usageFromInteraction(interaction, streamedSearches) {
7351
+ const usage = interaction.usage;
7352
+ const searches = streamedSearches ?? countSearchesInSteps(interaction);
7353
+ return {
7354
+ inputTokens: usage?.total_input_tokens ?? 0,
7355
+ outputTokens: usage?.total_output_tokens ?? 0,
7356
+ totalTokens: usage?.total_tokens ?? 0,
7357
+ cachedInputTokens: usage?.total_cached_tokens,
7358
+ reasoningTokens: usage?.total_thought_tokens,
7359
+ ...searches > 0 ? { searches } : {}
7360
+ };
7361
+ }
7362
+ function doneEventFromInteraction(interaction, streamedSearches) {
7363
+ const { report, citations } = extractReportFromInteraction(interaction);
7364
+ return {
7365
+ type: "done",
7366
+ result: {
7367
+ status: mapInteractionStatus(interaction.status),
7368
+ report,
7369
+ citations,
7370
+ usage: usageFromInteraction(interaction, streamedSearches),
7371
+ raw: interaction
7372
+ }
7373
+ };
7374
+ }
7375
+ async function* normalizeInteractionsStream(stream2, fetchFinal) {
7376
+ let streamedSearches = 0;
7377
+ const countedSearchSteps = /* @__PURE__ */ new Set();
7378
+ let interactionId;
7379
+ for await (const event of stream2) {
7380
+ const cursor = event.event_id;
7381
+ switch (event.event_type) {
7382
+ case "interaction.created": {
7383
+ const interaction = event.interaction;
7384
+ interactionId = interaction.id;
7385
+ yield { type: "created", jobId: interaction.id, cursor, rawEvent: event };
7386
+ yield { type: "status", status: mapInteractionStatus(interaction.status), cursor };
7387
+ break;
7388
+ }
7389
+ case "interaction.status_update":
7390
+ yield {
7391
+ type: "status",
7392
+ status: mapInteractionStatus(event.status),
7393
+ cursor,
7394
+ rawEvent: event
7395
+ };
7396
+ break;
7397
+ case "step.start": {
7398
+ const step = event.step;
7399
+ if (step.type === "thought") {
7400
+ yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
7401
+ } else if (step.type === "model_output") {
7402
+ yield { type: "phase", phase: "writing", cursor, rawEvent: event };
7403
+ } else if (step.type === "google_search_call") {
7404
+ if (!countedSearchSteps.has(event.index)) {
7405
+ countedSearchSteps.add(event.index);
7406
+ streamedSearches += step.arguments?.queries?.length ?? 1;
7407
+ }
7408
+ yield { type: "phase", phase: "searching", cursor, rawEvent: event };
7409
+ yield {
7410
+ type: "search",
7411
+ action: "search",
7412
+ status: "started",
7413
+ query: step.arguments?.queries?.join("; "),
7414
+ cursor
7415
+ };
7416
+ } else if (step.type === "google_search_result") {
7417
+ yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
7418
+ }
7419
+ break;
7420
+ }
7421
+ case "step.delta":
7422
+ yield* normalizeDelta(event, cursor, (queries) => {
7423
+ if (!countedSearchSteps.has(event.index)) {
7424
+ countedSearchSteps.add(event.index);
7425
+ streamedSearches += queries;
7426
+ }
7427
+ });
7428
+ break;
7429
+ case "step.stop":
7430
+ break;
7431
+ case "interaction.completed": {
7432
+ let interaction = event.interaction;
7433
+ const finalId = interaction.id ?? interactionId;
7434
+ if (fetchFinal && finalId !== void 0 && !interaction.usage?.total_tokens) {
7435
+ try {
7436
+ interaction = await fetchFinal(finalId);
7437
+ } catch {
7438
+ }
7439
+ }
7440
+ const searches = streamedSearches > 0 ? streamedSearches : void 0;
7441
+ yield {
7442
+ type: "usage",
7443
+ usage: usageFromInteraction(interaction, searches),
7444
+ cursor,
7445
+ rawEvent: event
7446
+ };
7447
+ yield { ...doneEventFromInteraction(interaction, searches), cursor };
7448
+ break;
7449
+ }
7450
+ case "error":
7451
+ if (event.error?.code === "client_closed_request") break;
7452
+ yield {
7453
+ type: "error",
7454
+ error: {
7455
+ message: event.error?.message ?? "Gemini interaction error",
7456
+ code: event.error?.code !== void 0 ? String(event.error.code) : void 0,
7457
+ retryable: isRetryableError(new Error(event.error?.message ?? ""))
7458
+ },
7459
+ cursor,
7460
+ rawEvent: event
7461
+ };
7462
+ break;
7463
+ default: {
7464
+ const type = event.event_type ?? "";
7465
+ if (type.endsWith("error")) {
7466
+ const err = event.error;
7467
+ if (err?.code === "client_closed_request") break;
7468
+ yield {
7469
+ type: "error",
7470
+ error: { message: err?.message ?? "Gemini interaction error", retryable: false },
7471
+ cursor,
7472
+ rawEvent: event
7473
+ };
7474
+ }
7475
+ break;
7476
+ }
7477
+ }
7478
+ }
7479
+ }
7480
+ function* normalizeDelta(event, cursor, onSearchCall) {
7481
+ const delta = event.delta;
7482
+ switch (delta.type) {
7483
+ case "text": {
7484
+ const textDelta = delta;
7485
+ yield { type: "text", delta: textDelta.text, cursor, rawEvent: event };
7486
+ break;
7487
+ }
7488
+ // Citations arrive as dedicated annotation deltas in the current schema.
7489
+ case "text_annotation_delta": {
7490
+ const annotationDelta = delta;
7491
+ for (const annotation of annotationDelta.annotations ?? []) {
7492
+ const citation = annotationToCitation(annotation);
7493
+ if (citation) {
7494
+ yield { type: "citation", citation, cursor, rawEvent: event };
7495
+ }
7496
+ }
7497
+ break;
7498
+ }
7499
+ // The SDK emits "thought_summary"; some preview docs show "thought" —
7500
+ // handle both defensively.
7501
+ case "thought_summary":
7502
+ case "thought": {
7503
+ const thought = delta;
7504
+ const text3 = thought.content?.type === "text" ? thought.content.text ?? "" : "";
7505
+ if (text3.length > 0) {
7506
+ yield { type: "thinking", delta: text3, cursor, rawEvent: event };
7507
+ }
7508
+ break;
7509
+ }
7510
+ case "thought_signature":
7511
+ break;
7512
+ case "google_search_call": {
7513
+ const call = delta;
7514
+ onSearchCall(call.arguments?.queries?.length ?? 1);
7515
+ yield {
7516
+ type: "search",
7517
+ action: "search",
7518
+ status: "started",
7519
+ query: call.arguments?.queries?.join("; "),
7520
+ cursor,
7521
+ rawEvent: event
7522
+ };
7523
+ break;
7524
+ }
7525
+ case "google_search_result": {
7526
+ const result = delta;
7527
+ yield {
7528
+ type: "search",
7529
+ action: "search",
7530
+ status: "completed",
7531
+ url: result.result?.[0]?.url,
7532
+ cursor,
7533
+ rawEvent: event
7534
+ };
7535
+ break;
7536
+ }
7537
+ case "url_context_call": {
7538
+ const call = delta;
7539
+ yield {
7540
+ type: "search",
7541
+ action: "open_page",
7542
+ status: "started",
7543
+ url: call.arguments?.urls?.[0],
7544
+ cursor,
7545
+ rawEvent: event
7546
+ };
7547
+ break;
7548
+ }
7549
+ case "url_context_result":
7550
+ yield { type: "search", action: "open_page", status: "completed", cursor, rawEvent: event };
7551
+ break;
7552
+ case "code_execution_call":
7553
+ yield { type: "tool", tool: "code_interpreter", status: "started", cursor, rawEvent: event };
7554
+ break;
7555
+ case "code_execution_result":
7556
+ yield {
7557
+ type: "tool",
7558
+ tool: "code_interpreter",
7559
+ status: "completed",
7560
+ cursor,
7561
+ rawEvent: event
7562
+ };
7563
+ break;
7564
+ case "file_search_call":
7565
+ yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
7566
+ break;
7567
+ case "file_search_result":
7568
+ yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
7569
+ break;
7570
+ case "mcp_server_tool_call":
7571
+ yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
7572
+ break;
7573
+ case "mcp_server_tool_result":
7574
+ yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
7575
+ break;
7576
+ default:
7577
+ break;
7578
+ }
7579
+ }
7580
+ function abortError() {
7581
+ const error = new Error("The operation was aborted");
7582
+ error.name = "AbortError";
7583
+ return error;
7584
+ }
7585
+ function sleep(ms, signal) {
7586
+ return new Promise((resolve2, reject) => {
7587
+ if (signal?.aborted) {
7588
+ reject(abortError());
7589
+ return;
7590
+ }
7591
+ const timer = setTimeout(() => {
7592
+ signal?.removeEventListener("abort", onAbort);
7593
+ resolve2();
7594
+ }, ms);
7595
+ timer.unref?.();
7596
+ const onAbort = () => {
7597
+ clearTimeout(timer);
7598
+ reject(abortError());
7599
+ };
7600
+ signal?.addEventListener("abort", onAbort, { once: true });
7601
+ });
7602
+ }
7603
+ async function withRetries(fn, maxRetries, signal) {
7604
+ let attempt = 0;
7605
+ while (true) {
7606
+ try {
7607
+ return await fn();
7608
+ } catch (error) {
7609
+ const err = error instanceof Error ? error : new Error(String(error));
7610
+ if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
7611
+ throw err;
7612
+ }
7613
+ attempt += 1;
7614
+ await sleep(extractRetryAfterMs(err) ?? RESEARCH_POLL_INTERVAL_MS, signal);
7615
+ }
7616
+ }
7617
+ }
7618
+ async function* withTerminalReconciliation(events, client, jobIdHint, signal) {
7619
+ let jobId = jobIdHint;
7620
+ let sawDone = false;
7621
+ for await (const event of events) {
7622
+ if (event.type === "created" && event.jobId !== null) jobId = event.jobId;
7623
+ if (event.type === "done") sawDone = true;
7624
+ yield event;
7625
+ }
7626
+ if (sawDone || jobId === void 0) return;
7627
+ const fetchInteraction = finalInteractionFetcher(client, signal);
7628
+ while (true) {
7629
+ const interaction = await fetchInteraction(jobId);
7630
+ const status = mapInteractionStatus(interaction.status);
7631
+ yield { type: "status", status };
7632
+ if (isTerminalStatus(status)) {
7633
+ yield { type: "usage", usage: usageFromInteraction(interaction) };
7634
+ yield doneEventFromInteraction(interaction);
7635
+ return;
7636
+ }
7637
+ await sleep(RESEARCH_POLL_INTERVAL_MS, signal);
7638
+ }
7639
+ }
7640
+ function startGeminiResearch(client, options, agentId, _spec) {
7641
+ const request = buildGeminiResearchRequest(options, agentId);
7642
+ return (async function* () {
7643
+ const interaction = await withRetries(
7644
+ () => client.interactions.create(request, requestOptions(options.signal)),
7645
+ GEMINI_RESEARCH_CREATE_MAX_RETRIES,
7646
+ options.signal
7647
+ );
7648
+ if (interaction.id === void 0) {
7649
+ throw new Error(
7650
+ "Gemini interactions.create returned no interaction id \u2014 cannot stream events."
7651
+ );
7652
+ }
7653
+ yield { type: "created", jobId: interaction.id, rawEvent: interaction };
7654
+ yield { type: "status", status: mapInteractionStatus(interaction.status) };
7655
+ const stream2 = await client.interactions.get(
7656
+ interaction.id,
7657
+ { stream: true },
7658
+ requestOptions(options.signal)
7659
+ );
7660
+ for await (const event of withTerminalReconciliation(
7661
+ normalizeInteractionsStream(stream2, finalInteractionFetcher(client, options.signal)),
7662
+ client,
7663
+ interaction.id,
7664
+ options.signal
7665
+ )) {
7666
+ if (event.type === "created") continue;
7667
+ yield event;
7668
+ }
7669
+ })();
7670
+ }
7671
+ function isTerminalStatus(status) {
7672
+ return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete" || status === "budget_exceeded";
7673
+ }
7674
+ function resumeGeminiResearch(client, ref, signal) {
7675
+ return (async function* () {
7676
+ const current = await client.interactions.get(
7677
+ ref.jobId,
7678
+ void 0,
7679
+ requestOptions(signal)
7680
+ );
7681
+ const status = mapInteractionStatus(current.status);
7682
+ if (isTerminalStatus(status)) {
7683
+ yield { type: "status", status };
7684
+ const { report, citations } = extractReportFromInteraction(current);
7685
+ if (report.length > 0) {
7686
+ yield { type: "text", delta: report };
7687
+ }
7688
+ for (const citation of citations) {
7689
+ yield { type: "citation", citation };
7690
+ }
7691
+ yield { type: "usage", usage: usageFromInteraction(current) };
7692
+ yield doneEventFromInteraction(current);
7693
+ return;
7694
+ }
7695
+ const stream2 = await client.interactions.get(
7696
+ ref.jobId,
7697
+ { stream: true, ...ref.cursor !== void 0 ? { last_event_id: ref.cursor } : {} },
7698
+ requestOptions(signal)
7699
+ );
7700
+ yield* withTerminalReconciliation(
7701
+ normalizeInteractionsStream(stream2, finalInteractionFetcher(client, signal)),
7702
+ client,
7703
+ ref.jobId,
7704
+ signal
7705
+ );
7706
+ })();
7707
+ }
7708
+ async function getGeminiResearchStatus(client, ref) {
7709
+ const interaction = await client.interactions.get(
7710
+ ref.jobId
7711
+ );
7712
+ const status = mapInteractionStatus(interaction.status);
7713
+ if (!isTerminalStatus(status)) {
7714
+ return { status };
7715
+ }
7716
+ const { report, citations } = extractReportFromInteraction(interaction);
7717
+ return {
7718
+ status,
7719
+ result: {
7720
+ jobId: interaction.id,
7721
+ provider: "gemini",
7722
+ model: ref.model,
7723
+ status,
7724
+ report,
7725
+ citations,
7726
+ usage: usageFromInteraction(interaction),
7727
+ raw: interaction
7728
+ }
7729
+ };
7730
+ }
7731
+ async function cancelGeminiResearch(client, ref) {
7732
+ await client.interactions.cancel(ref.jobId);
7733
+ }
7734
+ var GEMINI_RESEARCH_CREATE_MAX_RETRIES, GEMINI_STATUS_VALUES, requestOptions, finalInteractionFetcher;
7735
+ var init_gemini_research = __esm({
7736
+ "src/providers/gemini-research.ts"() {
7737
+ "use strict";
7738
+ init_errors2();
7739
+ init_retry();
7740
+ init_constants3();
7741
+ init_errors3();
7742
+ GEMINI_RESEARCH_CREATE_MAX_RETRIES = 3;
7743
+ GEMINI_STATUS_VALUES = /* @__PURE__ */ new Set([
7744
+ "queued",
7745
+ "in_progress",
7746
+ "requires_action",
7747
+ "completed",
7748
+ "failed",
7749
+ "cancelled",
7750
+ "incomplete",
7751
+ "budget_exceeded"
7752
+ ]);
7753
+ requestOptions = (signal) => ({
7754
+ timeout: GEMINI_RESEARCH_MAX_DURATION_MS,
7755
+ signal,
7756
+ // The SDK's own retries would fight llmist's retry policy.
7757
+ maxRetries: 0
7758
+ });
7759
+ finalInteractionFetcher = (client, signal) => (id) => client.interactions.get(
7760
+ id,
7761
+ void 0,
7762
+ requestOptions(signal)
7763
+ );
7764
+ }
7765
+ });
7766
+
7767
+ // src/providers/gemini-research-models.ts
7768
+ function geminiResearchAgent(params) {
7769
+ return {
7770
+ provider: "gemini",
7771
+ modelId: params.agentId,
7772
+ kind: "agent",
7773
+ displayName: params.displayName,
7774
+ // Gemini 3.1 Pro base-tier token rates; >200K-context tier is higher.
7775
+ pricing: { input: 2, output: 12, perThousandSearches: 14 },
7776
+ capabilities: {
7777
+ streaming: true,
7778
+ // The Interactions API requires background execution for deep research.
7779
+ background: true,
7780
+ resumable: true,
7781
+ followUps: true,
7782
+ // Research tools are agent-managed — the tools option is not accepted.
7783
+ tools: []
7784
+ },
7785
+ // Interactions enforces a 60-minute research cap server-side.
7786
+ maxDurationMs: GEMINI_RESEARCH_MAX_DURATION_MS,
7787
+ metadata: params.notes ? { notes: params.notes } : void 0
7788
+ };
7789
+ }
7790
+ function isGeminiResearchModel(agentId) {
7791
+ return byId.has(agentId);
7792
+ }
7793
+ var geminiResearchModels, byId;
7794
+ var init_gemini_research_models = __esm({
7795
+ "src/providers/gemini-research-models.ts"() {
7796
+ "use strict";
7797
+ init_constants3();
7798
+ geminiResearchModels = [
7799
+ geminiResearchAgent({
7800
+ agentId: "deep-research-preview-04-2026",
7801
+ displayName: "Gemini Deep Research (preview 04-2026)",
7802
+ notes: "Speed-optimized deep research agent on a Gemini 3.1 Pro core."
7803
+ }),
7804
+ geminiResearchAgent({
7805
+ agentId: "deep-research-max-preview-04-2026",
7806
+ displayName: "Gemini Deep Research Max (preview 04-2026)",
7807
+ notes: "Maximum-comprehensiveness variant; roughly 2x the searches and tokens per run."
7808
+ }),
7809
+ geminiResearchAgent({
7810
+ agentId: "deep-research-pro-preview-12-2025",
7811
+ displayName: "Gemini Deep Research Pro (preview 12-2025)",
7812
+ notes: "Original deep research agent (Gemini 3 Pro core); prefer the 04-2026 agents."
7813
+ })
7814
+ ];
7815
+ byId = new Map(geminiResearchModels.map((spec) => [spec.modelId, spec]));
7816
+ }
7817
+ });
7818
+
7200
7819
  // src/providers/gemini-speech-models.ts
7201
7820
  function getGeminiSpeechModelSpec(modelId) {
7202
7821
  return geminiSpeechModels.find((m) => m.modelId === modelId);
@@ -7394,6 +8013,8 @@ var init_gemini = __esm({
7394
8013
  init_gemini_cache_manager();
7395
8014
  init_gemini_image_models();
7396
8015
  init_gemini_models();
8016
+ init_gemini_research();
8017
+ init_gemini_research_models();
7397
8018
  init_gemini_speech_models();
7398
8019
  init_utils();
7399
8020
  GEMINI3_PRO_THINKING_LEVEL = {
@@ -7613,6 +8234,27 @@ var init_gemini = __esm({
7613
8234
  format: spec?.defaultFormat ?? "wav"
7614
8235
  };
7615
8236
  }
8237
+ // =========================================================================
8238
+ // Deep Research (Interactions API)
8239
+ // =========================================================================
8240
+ getResearchModelSpecs() {
8241
+ return geminiResearchModels;
8242
+ }
8243
+ supportsResearch(agentId) {
8244
+ return isGeminiResearchModel(agentId);
8245
+ }
8246
+ startResearch(options, descriptor, spec) {
8247
+ return startGeminiResearch(this.client, options, descriptor.name, spec);
8248
+ }
8249
+ resumeResearch(ref, signal) {
8250
+ return resumeGeminiResearch(this.client, ref, signal);
8251
+ }
8252
+ getResearchStatus(ref) {
8253
+ return getGeminiResearchStatus(this.client, ref);
8254
+ }
8255
+ cancelResearch(ref) {
8256
+ return cancelGeminiResearch(this.client, ref);
8257
+ }
7616
8258
  buildApiRequest(options, descriptor, _spec, messages) {
7617
8259
  const contents = this.convertMessagesToContents(messages);
7618
8260
  return this.buildApiRequestFromContents(options, descriptor, _spec, contents, null, 0);
@@ -8839,17 +9481,17 @@ var init_openai_compatible_provider = __esm({
8839
9481
  async executeStreamRequest(payload, signal) {
8840
9482
  const client = this.client;
8841
9483
  const headers = this.getCustomHeaders();
8842
- const requestOptions = {};
9484
+ const requestOptions3 = {};
8843
9485
  if (signal) {
8844
- requestOptions.signal = signal;
9486
+ requestOptions3.signal = signal;
8845
9487
  }
8846
9488
  if (Object.keys(headers).length > 0) {
8847
- requestOptions.headers = headers;
9489
+ requestOptions3.headers = headers;
8848
9490
  }
8849
9491
  try {
8850
9492
  const stream2 = await client.chat.completions.create(
8851
9493
  payload,
8852
- Object.keys(requestOptions).length > 0 ? requestOptions : void 0
9494
+ Object.keys(requestOptions3).length > 0 ? requestOptions3 : void 0
8853
9495
  );
8854
9496
  return stream2;
8855
9497
  } catch (error) {
@@ -9726,6 +10368,549 @@ var init_openai_models = __esm({
9726
10368
  }
9727
10369
  });
9728
10370
 
10371
+ // src/providers/openai-research.ts
10372
+ function buildTools(options) {
10373
+ const tools = [];
10374
+ for (const tool of options.tools ?? []) {
10375
+ switch (tool.type) {
10376
+ case "web_search":
10377
+ tools.push({ type: "web_search_preview" });
10378
+ break;
10379
+ case "file_search":
10380
+ if (tool.vectorStoreIds.length > OPENAI_FILE_SEARCH_MAX_VECTOR_STORES) {
10381
+ throw new ResearchValidationError(
10382
+ `OpenAI file_search accepts at most ${OPENAI_FILE_SEARCH_MAX_VECTOR_STORES} vector stores (got ${tool.vectorStoreIds.length}).`
10383
+ );
10384
+ }
10385
+ tools.push({ type: "file_search", vector_store_ids: tool.vectorStoreIds });
10386
+ break;
10387
+ case "mcp":
10388
+ tools.push({
10389
+ type: "mcp",
10390
+ server_label: tool.serverLabel,
10391
+ server_url: tool.serverUrl,
10392
+ require_approval: tool.requireApproval ?? "never"
10393
+ });
10394
+ break;
10395
+ case "code_interpreter":
10396
+ tools.push({ type: "code_interpreter", container: { type: "auto" } });
10397
+ break;
10398
+ }
10399
+ }
10400
+ return tools;
10401
+ }
10402
+ function buildResponsesResearchRequest(options, spec, modelId) {
10403
+ const tools = buildTools(options);
10404
+ const hasDataSource = (options.tools ?? []).some(
10405
+ (tool) => ["web_search", "file_search", "mcp"].includes(tool.type)
10406
+ );
10407
+ if (!hasDataSource) {
10408
+ throw new ResearchValidationError(
10409
+ "OpenAI deep research requires at least one data-source tool (web_search, file_search, or mcp)."
10410
+ );
10411
+ }
10412
+ const background = options.background ?? spec?.capabilities.background ?? true;
10413
+ const input = options.systemPrompt ? `${options.systemPrompt}
10414
+
10415
+ ${options.query}` : options.query;
10416
+ const request = {
10417
+ model: modelId,
10418
+ input,
10419
+ background,
10420
+ // Background mode requires stored responses; polling/resume need them too.
10421
+ ...background ? { store: true } : {},
10422
+ reasoning: {
10423
+ summary: "auto",
10424
+ ...options.reasoning?.effort && options.reasoning.effort !== "none" ? { effort: mapEffort(options.reasoning.effort) } : {}
10425
+ },
10426
+ tools,
10427
+ ...options.maxToolCalls !== void 0 ? { max_tool_calls: options.maxToolCalls } : {},
10428
+ ...options.extra ?? {}
10429
+ };
10430
+ return request;
10431
+ }
10432
+ function mapEffort(effort) {
10433
+ switch (effort) {
10434
+ case "maximum":
10435
+ return "xhigh";
10436
+ case "low":
10437
+ case "medium":
10438
+ case "high":
10439
+ return effort;
10440
+ default:
10441
+ return "medium";
10442
+ }
10443
+ }
10444
+ function extractReportFromResponse(response) {
10445
+ const citations = [];
10446
+ const parts = [];
10447
+ for (const item of response.output ?? []) {
10448
+ if (item.type !== "message") continue;
10449
+ for (const content of item.content ?? []) {
10450
+ if (content.type !== "output_text") continue;
10451
+ parts.push(content.text);
10452
+ for (const annotation of content.annotations ?? []) {
10453
+ const citation = annotationToCitation2(annotation);
10454
+ if (citation) citations.push(citation);
10455
+ }
10456
+ }
10457
+ }
10458
+ return { report: parts.join(""), citations };
10459
+ }
10460
+ function annotationToCitation2(annotation) {
10461
+ if (typeof annotation !== "object" || annotation === null) return void 0;
10462
+ const a = annotation;
10463
+ if (a.type !== "url_citation" || typeof a.url !== "string") return void 0;
10464
+ return {
10465
+ url: a.url,
10466
+ title: typeof a.title === "string" ? a.title : void 0,
10467
+ startIndex: typeof a.start_index === "number" ? a.start_index : void 0,
10468
+ endIndex: typeof a.end_index === "number" ? a.end_index : void 0
10469
+ };
10470
+ }
10471
+ function countSearches(response) {
10472
+ let searches = 0;
10473
+ for (const item of response.output ?? []) {
10474
+ if (item.type === "web_search_call") searches += 1;
10475
+ }
10476
+ return searches;
10477
+ }
10478
+ function usageFromResponse(response) {
10479
+ const usage = response.usage;
10480
+ return {
10481
+ inputTokens: usage?.input_tokens ?? 0,
10482
+ outputTokens: usage?.output_tokens ?? 0,
10483
+ totalTokens: usage?.total_tokens ?? 0,
10484
+ cachedInputTokens: usage?.input_tokens_details?.cached_tokens,
10485
+ reasoningTokens: usage?.output_tokens_details?.reasoning_tokens,
10486
+ searches: countSearches(response)
10487
+ };
10488
+ }
10489
+ function mapResponseStatus(status) {
10490
+ return status && STATUS_MAP[status] || "in_progress";
10491
+ }
10492
+ function doneEventFromResponse(response) {
10493
+ const { report, citations } = extractReportFromResponse(response);
10494
+ return {
10495
+ type: "done",
10496
+ result: {
10497
+ status: mapResponseStatus(response.status),
10498
+ report,
10499
+ citations,
10500
+ usage: usageFromResponse(response),
10501
+ raw: response
10502
+ }
10503
+ };
10504
+ }
10505
+ async function* normalizeResponsesStream(stream2) {
10506
+ const searchStarted = /* @__PURE__ */ new Set();
10507
+ for await (const event of stream2) {
10508
+ const cursor = String(event.sequence_number);
10509
+ switch (event.type) {
10510
+ case "response.created":
10511
+ yield { type: "created", jobId: event.response.id, cursor, rawEvent: event };
10512
+ yield { type: "status", status: mapResponseStatus(event.response.status), cursor };
10513
+ break;
10514
+ case "response.queued":
10515
+ yield { type: "status", status: "queued", cursor, rawEvent: event };
10516
+ break;
10517
+ case "response.in_progress":
10518
+ yield { type: "status", status: "in_progress", cursor, rawEvent: event };
10519
+ break;
10520
+ case "response.output_item.added": {
10521
+ const item = event.item;
10522
+ if (item.type === "reasoning") {
10523
+ yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
10524
+ } else if (item.type === "web_search_call") {
10525
+ const action = "action" in item ? item.action : void 0;
10526
+ searchStarted.add(item.id);
10527
+ yield {
10528
+ type: "search",
10529
+ action: action?.type ?? "search",
10530
+ status: "started",
10531
+ query: action && "query" in action ? action.query ?? void 0 : void 0,
10532
+ url: action && "url" in action ? action.url ?? void 0 : void 0,
10533
+ cursor,
10534
+ rawEvent: event
10535
+ };
10536
+ } else if (item.type === "code_interpreter_call") {
10537
+ yield {
10538
+ type: "tool",
10539
+ tool: "code_interpreter",
10540
+ status: "started",
10541
+ cursor,
10542
+ rawEvent: event
10543
+ };
10544
+ } else if (item.type === "file_search_call") {
10545
+ yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
10546
+ } else if (item.type === "mcp_call") {
10547
+ yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
10548
+ } else if (item.type === "message") {
10549
+ yield { type: "phase", phase: "writing", cursor, rawEvent: event };
10550
+ }
10551
+ break;
10552
+ }
10553
+ case "response.web_search_call.in_progress":
10554
+ case "response.web_search_call.searching":
10555
+ if (!searchStarted.has(event.item_id)) {
10556
+ searchStarted.add(event.item_id);
10557
+ yield { type: "search", action: "search", status: "started", cursor, rawEvent: event };
10558
+ }
10559
+ break;
10560
+ case "response.web_search_call.completed":
10561
+ yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
10562
+ break;
10563
+ case "response.code_interpreter_call.completed":
10564
+ yield {
10565
+ type: "tool",
10566
+ tool: "code_interpreter",
10567
+ status: "completed",
10568
+ cursor,
10569
+ rawEvent: event
10570
+ };
10571
+ break;
10572
+ case "response.file_search_call.completed":
10573
+ yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
10574
+ break;
10575
+ case "response.mcp_call.completed":
10576
+ yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
10577
+ break;
10578
+ case "response.reasoning_summary_text.delta":
10579
+ yield { type: "thinking", delta: event.delta, cursor, rawEvent: event };
10580
+ break;
10581
+ case "response.output_text.delta":
10582
+ yield { type: "text", delta: event.delta, cursor, rawEvent: event };
10583
+ break;
10584
+ case "response.output_text.annotation.added": {
10585
+ const citation = annotationToCitation2(event.annotation);
10586
+ if (citation) {
10587
+ yield { type: "citation", citation, cursor, rawEvent: event };
10588
+ }
10589
+ break;
10590
+ }
10591
+ case "response.completed":
10592
+ case "response.failed":
10593
+ case "response.incomplete": {
10594
+ const response = event.response;
10595
+ yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
10596
+ if (event.type === "response.failed") {
10597
+ const message = response.error?.message ?? "OpenAI research run failed";
10598
+ yield {
10599
+ type: "error",
10600
+ error: { message, code: response.error?.code ?? void 0, retryable: false },
10601
+ cursor
10602
+ };
10603
+ }
10604
+ yield { ...doneEventFromResponse(response), cursor };
10605
+ break;
10606
+ }
10607
+ case "error": {
10608
+ const err = new Error(event.message ?? "OpenAI stream error");
10609
+ yield {
10610
+ type: "error",
10611
+ error: {
10612
+ message: event.message ?? "OpenAI stream error",
10613
+ code: event.code ?? void 0,
10614
+ retryable: isRetryableError(err)
10615
+ },
10616
+ cursor,
10617
+ rawEvent: event
10618
+ };
10619
+ break;
10620
+ }
10621
+ default: {
10622
+ const type = event.type;
10623
+ if (type === "response.cancelled") {
10624
+ const response = event.response;
10625
+ yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
10626
+ yield { ...doneEventFromResponse(response), cursor };
10627
+ }
10628
+ break;
10629
+ }
10630
+ }
10631
+ }
10632
+ }
10633
+ function sleep2(ms, signal) {
10634
+ return new Promise((resolve2, reject) => {
10635
+ if (signal?.aborted) {
10636
+ reject(abortError2());
10637
+ return;
10638
+ }
10639
+ const timer = setTimeout(() => {
10640
+ signal?.removeEventListener("abort", onAbort);
10641
+ resolve2();
10642
+ }, ms);
10643
+ timer.unref?.();
10644
+ const onAbort = () => {
10645
+ clearTimeout(timer);
10646
+ reject(abortError2());
10647
+ };
10648
+ signal?.addEventListener("abort", onAbort, { once: true });
10649
+ });
10650
+ }
10651
+ function abortError2() {
10652
+ const error = new Error("The operation was aborted");
10653
+ error.name = "AbortError";
10654
+ return error;
10655
+ }
10656
+ async function withRetries2(fn, maxRetries, signal) {
10657
+ let attempt = 0;
10658
+ while (true) {
10659
+ try {
10660
+ return await fn();
10661
+ } catch (error) {
10662
+ const err = error instanceof Error ? error : new Error(String(error));
10663
+ if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
10664
+ throw err;
10665
+ }
10666
+ attempt += 1;
10667
+ const retryAfter = extractRetryAfterMs(err);
10668
+ await sleep2(retryAfter ?? RESEARCH_POLL_INTERVAL_MS, signal);
10669
+ }
10670
+ }
10671
+ }
10672
+ function startOpenAIResearch(client, options, modelId, spec) {
10673
+ const request = buildResponsesResearchRequest(options, spec, modelId);
10674
+ const streaming = spec?.capabilities.streaming ?? true;
10675
+ if (streaming) {
10676
+ return (async function* () {
10677
+ const stream2 = await withRetries2(
10678
+ () => client.responses.create(
10679
+ { ...request, stream: true },
10680
+ requestOptions2(options.signal)
10681
+ ),
10682
+ OPENAI_RESEARCH_CREATE_MAX_RETRIES,
10683
+ options.signal
10684
+ );
10685
+ yield* normalizeResponsesStream(stream2);
10686
+ })();
10687
+ }
10688
+ return (async function* () {
10689
+ const created = await withRetries2(
10690
+ () => client.responses.create(
10691
+ { ...request, background: true, store: true, stream: false },
10692
+ requestOptions2(options.signal)
10693
+ ),
10694
+ OPENAI_RESEARCH_CREATE_MAX_RETRIES,
10695
+ options.signal
10696
+ );
10697
+ yield { type: "created", jobId: created.id, rawEvent: created };
10698
+ yield* pollResponseToCompletion(client, created.id, created, options.signal);
10699
+ })();
10700
+ }
10701
+ function* emitTerminalResponse(response) {
10702
+ const status = mapResponseStatus(response.status);
10703
+ const { report, citations } = extractReportFromResponse(response);
10704
+ if (report.length > 0) {
10705
+ yield { type: "text", delta: report };
10706
+ }
10707
+ for (const citation of citations) {
10708
+ yield { type: "citation", citation };
10709
+ }
10710
+ yield { type: "usage", usage: usageFromResponse(response) };
10711
+ if (status === "failed") {
10712
+ yield {
10713
+ type: "error",
10714
+ error: {
10715
+ message: response.error?.message ?? "OpenAI research run failed",
10716
+ code: response.error?.code ?? void 0,
10717
+ retryable: false
10718
+ }
10719
+ };
10720
+ }
10721
+ yield doneEventFromResponse(response);
10722
+ }
10723
+ async function* pollResponseToCompletion(client, responseId, initial, signal) {
10724
+ let interval = RESEARCH_POLL_INTERVAL_MS;
10725
+ let current = initial;
10726
+ while (true) {
10727
+ const status = mapResponseStatus(current?.status);
10728
+ yield { type: "status", status, rawEvent: current };
10729
+ if (current && isTerminalStatus2(status)) {
10730
+ yield* emitTerminalResponse(current);
10731
+ return;
10732
+ }
10733
+ await sleep2(interval, signal);
10734
+ interval = Math.min(interval * RESEARCH_POLL_BACKOFF_FACTOR, RESEARCH_POLL_MAX_INTERVAL_MS);
10735
+ current = await withRetries2(
10736
+ () => client.responses.retrieve(responseId, {}, requestOptions2(signal)),
10737
+ OPENAI_RESEARCH_POLL_MAX_RETRIES,
10738
+ signal
10739
+ );
10740
+ }
10741
+ }
10742
+ function isTerminalStatus2(status) {
10743
+ return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete";
10744
+ }
10745
+ function resumeOpenAIResearch(client, ref, spec, signal) {
10746
+ const streaming = spec?.capabilities.streaming ?? true;
10747
+ if (streaming) {
10748
+ return (async function* () {
10749
+ const current = await withRetries2(
10750
+ () => client.responses.retrieve(ref.jobId, {}, requestOptions2(signal)),
10751
+ OPENAI_RESEARCH_POLL_MAX_RETRIES,
10752
+ signal
10753
+ );
10754
+ if (isTerminalStatus2(mapResponseStatus(current.status))) {
10755
+ yield { type: "status", status: mapResponseStatus(current.status) };
10756
+ yield* emitTerminalResponse(current);
10757
+ return;
10758
+ }
10759
+ const stream2 = await client.responses.retrieve(
10760
+ ref.jobId,
10761
+ {
10762
+ stream: true,
10763
+ ...ref.cursor !== void 0 ? { starting_after: Number(ref.cursor) } : {}
10764
+ },
10765
+ requestOptions2(signal)
10766
+ );
10767
+ yield* normalizeResponsesStream(stream2);
10768
+ })();
10769
+ }
10770
+ return pollResponseToCompletion(client, ref.jobId, void 0, signal);
10771
+ }
10772
+ async function getOpenAIResearchStatus(client, ref) {
10773
+ const response = await client.responses.retrieve(ref.jobId, {});
10774
+ const status = mapResponseStatus(response.status);
10775
+ if (!isTerminalStatus2(status)) {
10776
+ return { status };
10777
+ }
10778
+ const { report, citations } = extractReportFromResponse(response);
10779
+ return {
10780
+ status,
10781
+ result: {
10782
+ jobId: response.id,
10783
+ provider: "openai",
10784
+ model: ref.model,
10785
+ status,
10786
+ report,
10787
+ citations,
10788
+ usage: usageFromResponse(response),
10789
+ raw: response
10790
+ }
10791
+ };
10792
+ }
10793
+ async function cancelOpenAIResearch(client, ref) {
10794
+ await client.responses.cancel(ref.jobId);
10795
+ }
10796
+ var OPENAI_FILE_SEARCH_MAX_VECTOR_STORES, OPENAI_RESEARCH_CREATE_MAX_RETRIES, OPENAI_RESEARCH_POLL_MAX_RETRIES, STATUS_MAP, requestOptions2;
10797
+ var init_openai_research = __esm({
10798
+ "src/providers/openai-research.ts"() {
10799
+ "use strict";
10800
+ init_errors2();
10801
+ init_retry();
10802
+ init_constants3();
10803
+ init_errors3();
10804
+ OPENAI_FILE_SEARCH_MAX_VECTOR_STORES = 2;
10805
+ OPENAI_RESEARCH_CREATE_MAX_RETRIES = 3;
10806
+ OPENAI_RESEARCH_POLL_MAX_RETRIES = 5;
10807
+ STATUS_MAP = {
10808
+ queued: "queued",
10809
+ in_progress: "in_progress",
10810
+ completed: "completed",
10811
+ failed: "failed",
10812
+ cancelled: "cancelled",
10813
+ incomplete: "incomplete"
10814
+ };
10815
+ requestOptions2 = (signal) => ({
10816
+ timeout: OPENAI_RESEARCH_HTTP_TIMEOUT_MS,
10817
+ signal
10818
+ });
10819
+ }
10820
+ });
10821
+
10822
+ // src/providers/openai-research-models.ts
10823
+ function o3DeepResearch(modelId) {
10824
+ return {
10825
+ provider: "openai",
10826
+ modelId,
10827
+ kind: "model",
10828
+ displayName: "o3 Deep Research",
10829
+ contextWindow: 2e5,
10830
+ maxOutputTokens: 1e5,
10831
+ pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
10832
+ capabilities: {
10833
+ streaming: true,
10834
+ background: true,
10835
+ resumable: true,
10836
+ tools: DEEP_RESEARCH_TOOLS
10837
+ },
10838
+ requiredTools: [{ type: "web_search" }],
10839
+ metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
10840
+ };
10841
+ }
10842
+ function o4MiniDeepResearch(modelId) {
10843
+ return {
10844
+ provider: "openai",
10845
+ modelId,
10846
+ kind: "model",
10847
+ displayName: "o4-mini Deep Research",
10848
+ contextWindow: 2e5,
10849
+ maxOutputTokens: 1e5,
10850
+ pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
10851
+ capabilities: {
10852
+ streaming: true,
10853
+ background: true,
10854
+ resumable: true,
10855
+ tools: DEEP_RESEARCH_TOOLS
10856
+ },
10857
+ requiredTools: [{ type: "web_search" }],
10858
+ metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
10859
+ };
10860
+ }
10861
+ function getOpenAIResearchModelSpec(modelId) {
10862
+ return byId2.get(modelId);
10863
+ }
10864
+ function isOpenAIResearchModel(modelId) {
10865
+ return byId2.has(modelId);
10866
+ }
10867
+ var DEEP_RESEARCH_TOOLS, DEEP_RESEARCH_SHUTDOWN, openaiResearchModels, byId2;
10868
+ var init_openai_research_models = __esm({
10869
+ "src/providers/openai-research-models.ts"() {
10870
+ "use strict";
10871
+ DEEP_RESEARCH_TOOLS = [
10872
+ "web_search",
10873
+ "file_search",
10874
+ "mcp",
10875
+ "code_interpreter"
10876
+ ];
10877
+ DEEP_RESEARCH_SHUTDOWN = {
10878
+ deprecationDate: "2026-04-22",
10879
+ shutdownDate: "2026-07-23",
10880
+ replacement: "gpt-5.5-pro"
10881
+ };
10882
+ openaiResearchModels = [
10883
+ o3DeepResearch("o3-deep-research"),
10884
+ o3DeepResearch("o3-deep-research-2025-06-26"),
10885
+ o4MiniDeepResearch("o4-mini-deep-research"),
10886
+ o4MiniDeepResearch("o4-mini-deep-research-2025-06-26"),
10887
+ {
10888
+ provider: "openai",
10889
+ modelId: "gpt-5.5-pro",
10890
+ kind: "model",
10891
+ displayName: "GPT-5.5 Pro (research)",
10892
+ contextWindow: 105e4,
10893
+ maxOutputTokens: 128e3,
10894
+ pricing: { input: 30, output: 180, perThousandSearches: 10 },
10895
+ capabilities: {
10896
+ // No live streaming — runs execute via background create + poll.
10897
+ streaming: false,
10898
+ background: true,
10899
+ // Resumable in the attach sense: the poll loop re-attaches by job id.
10900
+ resumable: true,
10901
+ tools: DEEP_RESEARCH_TOOLS
10902
+ },
10903
+ requiredTools: [{ type: "web_search" }],
10904
+ metadata: {
10905
+ releaseDate: "2026-04-24",
10906
+ notes: "Designated replacement for the o3/o4-mini deep research models."
10907
+ }
10908
+ }
10909
+ ];
10910
+ byId2 = new Map(openaiResearchModels.map((spec) => [spec.modelId, spec]));
10911
+ }
10912
+ });
10913
+
9729
10914
  // src/providers/openai-speech-models.ts
9730
10915
  function getOpenAISpeechModelSpec(modelId) {
9731
10916
  return openaiSpeechModels.find((m) => m.modelId === modelId);
@@ -9881,6 +11066,8 @@ var init_openai = __esm({
9881
11066
  init_constants2();
9882
11067
  init_openai_image_models();
9883
11068
  init_openai_models();
11069
+ init_openai_research();
11070
+ init_openai_research_models();
9884
11071
  init_openai_speech_models();
9885
11072
  init_utils();
9886
11073
  ROLE_MAP2 = {
@@ -9984,6 +11171,32 @@ var init_openai = __esm({
9984
11171
  format: format2
9985
11172
  };
9986
11173
  }
11174
+ // =========================================================================
11175
+ // Deep Research (Responses API)
11176
+ // =========================================================================
11177
+ getResearchModelSpecs() {
11178
+ return openaiResearchModels;
11179
+ }
11180
+ supportsResearch(modelId) {
11181
+ return isOpenAIResearchModel(modelId);
11182
+ }
11183
+ startResearch(options, descriptor, spec) {
11184
+ return startOpenAIResearch(this.client, options, descriptor.name, spec);
11185
+ }
11186
+ resumeResearch(ref, signal) {
11187
+ return resumeOpenAIResearch(
11188
+ this.client,
11189
+ ref,
11190
+ getOpenAIResearchModelSpec(ref.model),
11191
+ signal
11192
+ );
11193
+ }
11194
+ getResearchStatus(ref) {
11195
+ return getOpenAIResearchStatus(this.client, ref);
11196
+ }
11197
+ cancelResearch(ref) {
11198
+ return cancelOpenAIResearch(this.client, ref);
11199
+ }
9987
11200
  buildApiRequest(options, descriptor, spec, messages) {
9988
11201
  const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
9989
11202
  const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
@@ -10679,35 +11892,246 @@ var init_openrouter_models = __esm({
10679
11892
  vision: false
10680
11893
  },
10681
11894
  metadata: {
10682
- family: "Mixtral",
10683
- notes: "Mixtral 8x22B via OpenRouter. Sparse MoE architecture."
11895
+ family: "Mixtral",
11896
+ notes: "Mixtral 8x22B via OpenRouter. Sparse MoE architecture."
11897
+ }
11898
+ },
11899
+ // ============================================================
11900
+ // Qwen Models (via OpenRouter)
11901
+ // ============================================================
11902
+ {
11903
+ provider: "openrouter",
11904
+ modelId: "qwen/qwen-2.5-72b-instruct",
11905
+ displayName: "Qwen 2.5 72B Instruct (OpenRouter)",
11906
+ contextWindow: 131072,
11907
+ maxOutputTokens: 8192,
11908
+ pricing: {
11909
+ input: 0.35,
11910
+ output: 0.4
11911
+ },
11912
+ knowledgeCutoff: "2024-09",
11913
+ features: {
11914
+ streaming: true,
11915
+ functionCalling: true,
11916
+ vision: false
11917
+ },
11918
+ metadata: {
11919
+ family: "Qwen 2.5",
11920
+ notes: "Qwen 2.5 72B via OpenRouter. Strong coding and math."
11921
+ }
11922
+ }
11923
+ ];
11924
+ }
11925
+ });
11926
+
11927
+ // src/providers/openrouter-research.ts
11928
+ function buildOpenRouterResearchMessages(options) {
11929
+ if (options.background === true) {
11930
+ throw new ResearchValidationError(
11931
+ "OpenRouter has no background mode for research runs \u2014 the stream must stay open."
11932
+ );
11933
+ }
11934
+ if (options.tools !== void 0 && options.tools.length > 0) {
11935
+ throw new ResearchValidationError(
11936
+ "OpenRouter research models manage their tools server-side \u2014 omit the tools option."
11937
+ );
11938
+ }
11939
+ const messages = [];
11940
+ if (options.systemPrompt) {
11941
+ messages.push({ role: "system", content: options.systemPrompt });
11942
+ }
11943
+ messages.push({ role: "user", content: options.query });
11944
+ return { messages };
11945
+ }
11946
+ function citationKey(citation) {
11947
+ return `${citation.url}#${citation.startIndex ?? ""}`;
11948
+ }
11949
+ function mapFinishReason(reason) {
11950
+ switch (reason) {
11951
+ case "length":
11952
+ return "incomplete";
11953
+ case "error":
11954
+ return "failed";
11955
+ default:
11956
+ return "completed";
11957
+ }
11958
+ }
11959
+ async function* normalizeOpenRouterResearchStream(stream2) {
11960
+ let createdEmitted = false;
11961
+ const seenCitations = /* @__PURE__ */ new Set();
11962
+ const seenCitationUrls = /* @__PURE__ */ new Set();
11963
+ const bufferedLegacyCitations = [];
11964
+ let usage;
11965
+ let terminalStatus;
11966
+ let lastRaw;
11967
+ for await (const chunk of stream2) {
11968
+ lastRaw = chunk;
11969
+ if (!createdEmitted) {
11970
+ createdEmitted = true;
11971
+ yield { type: "created", jobId: null, rawEvent: chunk };
11972
+ yield { type: "status", status: "in_progress" };
11973
+ }
11974
+ const choice = chunk.choices?.[0];
11975
+ const delta = choice?.delta ?? {};
11976
+ const detailTexts = (delta.reasoning_details ?? []).filter((detail) => typeof detail.text === "string" && detail.text.length > 0).map((detail) => detail.text);
11977
+ if (detailTexts.length > 0) {
11978
+ for (const text3 of detailTexts) {
11979
+ yield { type: "thinking", delta: text3, rawEvent: chunk };
11980
+ }
11981
+ } else if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
11982
+ yield { type: "thinking", delta: delta.reasoning, rawEvent: chunk };
11983
+ }
11984
+ if (typeof delta.content === "string" && delta.content.length > 0) {
11985
+ yield { type: "text", delta: delta.content, rawEvent: chunk };
11986
+ }
11987
+ const annotationSources = [
11988
+ ...delta.annotations ?? [],
11989
+ ...choice?.message?.annotations ?? []
11990
+ ];
11991
+ for (const annotation of annotationSources) {
11992
+ if (annotation.type !== "url_citation" || !annotation.url_citation?.url) continue;
11993
+ const citation = {
11994
+ url: annotation.url_citation.url,
11995
+ title: annotation.url_citation.title,
11996
+ content: annotation.url_citation.content,
11997
+ startIndex: annotation.url_citation.start_index,
11998
+ endIndex: annotation.url_citation.end_index
11999
+ };
12000
+ const key = citationKey(citation);
12001
+ if (!seenCitations.has(key)) {
12002
+ seenCitations.add(key);
12003
+ seenCitationUrls.add(citation.url);
12004
+ yield { type: "citation", citation, rawEvent: chunk };
12005
+ }
12006
+ }
12007
+ const legacyCitations = chunk.citations;
12008
+ if (Array.isArray(legacyCitations)) {
12009
+ for (const url of legacyCitations) {
12010
+ bufferedLegacyCitations.push({ url, rawEvent: chunk });
12011
+ }
12012
+ }
12013
+ if (choice?.finish_reason) {
12014
+ terminalStatus = mapFinishReason(choice.finish_reason);
12015
+ }
12016
+ const chunkUsage = chunk.usage;
12017
+ if (chunkUsage) {
12018
+ usage = {
12019
+ inputTokens: chunkUsage.prompt_tokens ?? 0,
12020
+ outputTokens: chunkUsage.completion_tokens ?? 0,
12021
+ totalTokens: chunkUsage.total_tokens ?? 0,
12022
+ cachedInputTokens: chunkUsage.prompt_tokens_details?.cached_tokens ?? void 0,
12023
+ reasoningTokens: chunkUsage.completion_tokens_details?.reasoning_tokens ?? void 0,
12024
+ searches: chunkUsage.num_search_queries ?? chunkUsage.server_tool_use?.web_search_requests ?? void 0,
12025
+ // Authoritative billed cost from OpenRouter usage accounting —
12026
+ // preferred over the catalog estimate (collector keeps it).
12027
+ costUSD: typeof chunkUsage.cost === "number" ? chunkUsage.cost : void 0
12028
+ };
12029
+ }
12030
+ }
12031
+ for (const { url, rawEvent } of bufferedLegacyCitations) {
12032
+ if (seenCitationUrls.has(url)) continue;
12033
+ seenCitationUrls.add(url);
12034
+ yield { type: "citation", citation: { url }, rawEvent };
12035
+ }
12036
+ if (usage) {
12037
+ yield { type: "usage", usage };
12038
+ }
12039
+ yield {
12040
+ type: "done",
12041
+ result: {
12042
+ status: terminalStatus ?? "completed",
12043
+ // Report text was streamed as deltas; the collector joins them.
12044
+ report: "",
12045
+ usage,
12046
+ raw: lastRaw
12047
+ }
12048
+ };
12049
+ }
12050
+ var init_openrouter_research = __esm({
12051
+ "src/providers/openrouter-research.ts"() {
12052
+ "use strict";
12053
+ init_errors3();
12054
+ }
12055
+ });
12056
+
12057
+ // src/providers/openrouter-research-models.ts
12058
+ function isOpenRouterResearchModel(modelId) {
12059
+ return byId3.has(modelId);
12060
+ }
12061
+ var OPENROUTER_STREAM_ONLY, openrouterResearchModels, byId3;
12062
+ var init_openrouter_research_models = __esm({
12063
+ "src/providers/openrouter-research-models.ts"() {
12064
+ "use strict";
12065
+ OPENROUTER_STREAM_ONLY = {
12066
+ streaming: true,
12067
+ background: false,
12068
+ resumable: false,
12069
+ tools: []
12070
+ };
12071
+ openrouterResearchModels = [
12072
+ {
12073
+ provider: "openrouter",
12074
+ modelId: "perplexity/sonar-deep-research",
12075
+ kind: "model",
12076
+ displayName: "Perplexity Sonar Deep Research",
12077
+ contextWindow: 128e3,
12078
+ pricing: {
12079
+ input: 2,
12080
+ output: 8,
12081
+ // Internal reasoning tokens are priced separately from output.
12082
+ internalReasoning: 3,
12083
+ perThousandSearches: 5
12084
+ },
12085
+ capabilities: OPENROUTER_STREAM_ONLY,
12086
+ metadata: {
12087
+ notes: "R1-style think-then-write: expect a long reasoning stream (100k+ tokens) before report text."
10684
12088
  }
10685
12089
  },
10686
- // ============================================================
10687
- // Qwen Models (via OpenRouter)
10688
- // ============================================================
10689
12090
  {
10690
12091
  provider: "openrouter",
10691
- modelId: "qwen/qwen-2.5-72b-instruct",
10692
- displayName: "Qwen 2.5 72B Instruct (OpenRouter)",
10693
- contextWindow: 131072,
10694
- maxOutputTokens: 8192,
10695
- pricing: {
10696
- input: 0.35,
10697
- output: 0.4
10698
- },
10699
- knowledgeCutoff: "2024-09",
10700
- features: {
10701
- streaming: true,
10702
- functionCalling: true,
10703
- vision: false
10704
- },
12092
+ modelId: "perplexity/sonar-pro-search",
12093
+ kind: "model",
12094
+ displayName: "Perplexity Sonar Pro Search",
12095
+ contextWindow: 2e5,
12096
+ pricing: { input: 3, output: 15, perThousandSearches: 18 },
12097
+ capabilities: OPENROUTER_STREAM_ONLY,
12098
+ metadata: { notes: "Agentic multi-step Pro Search \u2014 lighter than full deep research." }
12099
+ },
12100
+ {
12101
+ provider: "openrouter",
12102
+ modelId: "openai/o3-deep-research",
12103
+ kind: "model",
12104
+ displayName: "OpenAI o3 Deep Research (via OpenRouter)",
12105
+ contextWindow: 2e5,
12106
+ maxOutputTokens: 1e5,
12107
+ pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
12108
+ capabilities: OPENROUTER_STREAM_ONLY,
10705
12109
  metadata: {
10706
- family: "Qwen 2.5",
10707
- notes: "Qwen 2.5 72B via OpenRouter. Strong coding and math."
12110
+ // Upstream OpenAI shutdown propagates through OpenRouter.
12111
+ deprecationDate: "2026-04-22",
12112
+ shutdownDate: "2026-07-23",
12113
+ replacement: "perplexity/sonar-deep-research",
12114
+ notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
12115
+ }
12116
+ },
12117
+ {
12118
+ provider: "openrouter",
12119
+ modelId: "openai/o4-mini-deep-research",
12120
+ kind: "model",
12121
+ displayName: "OpenAI o4-mini Deep Research (via OpenRouter)",
12122
+ contextWindow: 2e5,
12123
+ maxOutputTokens: 1e5,
12124
+ pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
12125
+ capabilities: OPENROUTER_STREAM_ONLY,
12126
+ metadata: {
12127
+ deprecationDate: "2026-04-22",
12128
+ shutdownDate: "2026-07-23",
12129
+ replacement: "perplexity/sonar-deep-research",
12130
+ notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
10708
12131
  }
10709
12132
  }
10710
12133
  ];
12134
+ byId3 = new Map(openrouterResearchModels.map((spec) => [spec.modelId, spec]));
10711
12135
  }
10712
12136
  });
10713
12137
 
@@ -10838,8 +12262,11 @@ var init_openrouter = __esm({
10838
12262
  "use strict";
10839
12263
  init_model_shortcuts();
10840
12264
  init_logger();
12265
+ init_constants3();
10841
12266
  init_openai_compatible_provider();
10842
12267
  init_openrouter_models();
12268
+ init_openrouter_research();
12269
+ init_openrouter_research_models();
10843
12270
  init_openrouter_speech_models();
10844
12271
  init_utils();
10845
12272
  logger = createLogger({ name: "openrouter" });
@@ -10859,6 +12286,66 @@ var init_openrouter = __esm({
10859
12286
  getModelSpecs() {
10860
12287
  return OPENROUTER_MODELS;
10861
12288
  }
12289
+ // =========================================================================
12290
+ // Deep Research (chat-completions surface)
12291
+ //
12292
+ // Stream-only: no background mode, no job id, no resume. resumeResearch /
12293
+ // getResearchStatus / cancelResearch are deliberately NOT implemented — the
12294
+ // research job surfaces typed errors for those operations, and a dropped
12295
+ // stream is never silently re-run (a multi-dollar research run).
12296
+ // =========================================================================
12297
+ getResearchModelSpecs() {
12298
+ return openrouterResearchModels;
12299
+ }
12300
+ supportsResearch(modelId) {
12301
+ return isOpenRouterResearchModel(modelId);
12302
+ }
12303
+ startResearch(options, descriptor, _spec) {
12304
+ const { messages } = buildOpenRouterResearchMessages(options);
12305
+ const client = this.client;
12306
+ const request = {
12307
+ model: descriptor.name,
12308
+ messages,
12309
+ // Mandatory: multi-minute runs hit idle disconnects without streaming.
12310
+ stream: true,
12311
+ stream_options: { include_usage: true },
12312
+ // OpenRouter usage accounting: report the authoritative billed cost
12313
+ // (covers per-search fees that token-based estimation cannot see).
12314
+ usage: { include: true }
12315
+ };
12316
+ if (options.reasoning?.enabled !== void 0) {
12317
+ request.reasoning = {
12318
+ effort: OPENROUTER_EFFORT_MAP[options.reasoning.effort ?? "medium"]
12319
+ };
12320
+ }
12321
+ const protectedKeys = /* @__PURE__ */ new Set(["model", "messages", "stream", "stream_options", "usage"]);
12322
+ Object.assign(request, this.buildProviderSpecificParams(options.extra));
12323
+ for (const [key, value] of Object.entries(options.extra ?? {})) {
12324
+ if (!this.isProviderSpecificKey(key) && !protectedKeys.has(key)) {
12325
+ request[key] = value;
12326
+ }
12327
+ }
12328
+ const headers = this.getCustomHeaders();
12329
+ const requestOptions3 = {
12330
+ signal: options.signal,
12331
+ // Research runs take minutes — override the client's default timeout.
12332
+ timeout: OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS,
12333
+ ...Object.keys(headers).length > 0 ? { headers } : {}
12334
+ };
12335
+ const enhance = (error) => this.enhanceError(error);
12336
+ return (async function* () {
12337
+ let stream2;
12338
+ try {
12339
+ stream2 = await client.chat.completions.create(
12340
+ request,
12341
+ requestOptions3
12342
+ );
12343
+ } catch (error) {
12344
+ throw enhance(error);
12345
+ }
12346
+ yield* normalizeOpenRouterResearchStream(stream2);
12347
+ })();
12348
+ }
10862
12349
  /**
10863
12350
  * Override buildApiRequest to inject reasoning parameters and cache_control breakpoints.
10864
12351
  * OpenRouter normalizes reasoning into the standard OpenAI format,
@@ -11158,6 +12645,590 @@ var init_discovery = __esm({
11158
12645
  }
11159
12646
  });
11160
12647
 
12648
+ // src/research/cost.ts
12649
+ function estimateResearchCost(pricing, usage) {
12650
+ const cachedTokens = usage.cachedInputTokens ?? 0;
12651
+ const freshInputTokens = Math.max(0, usage.inputTokens - cachedTokens);
12652
+ const cachedRate = pricing.cachedInput ?? pricing.input;
12653
+ let outputTokens = usage.outputTokens;
12654
+ let reasoningCost = 0;
12655
+ if (pricing.internalReasoning !== void 0 && usage.reasoningTokens !== void 0) {
12656
+ outputTokens = Math.max(0, outputTokens - usage.reasoningTokens);
12657
+ reasoningCost = usage.reasoningTokens * pricing.internalReasoning / TOKENS_PER_MILLION;
12658
+ }
12659
+ const searchCost = pricing.perThousandSearches !== void 0 && usage.searches !== void 0 ? usage.searches * pricing.perThousandSearches / SEARCHES_PER_THOUSAND : 0;
12660
+ const total = freshInputTokens * pricing.input / TOKENS_PER_MILLION + cachedTokens * cachedRate / TOKENS_PER_MILLION + outputTokens * pricing.output / TOKENS_PER_MILLION + reasoningCost + searchCost;
12661
+ return Number(total.toFixed(RESEARCH_COST_DECIMALS));
12662
+ }
12663
+ var init_cost = __esm({
12664
+ "src/research/cost.ts"() {
12665
+ "use strict";
12666
+ init_constants3();
12667
+ }
12668
+ });
12669
+
12670
+ // src/research/collector.ts
12671
+ function citationKey2(citation) {
12672
+ return `${citation.url}#${citation.startIndex ?? ""}`;
12673
+ }
12674
+ function max(a, b) {
12675
+ if (a === void 0) return b;
12676
+ if (b === void 0) return a;
12677
+ return Math.max(a, b);
12678
+ }
12679
+ var EMPTY_USAGE, TERMINAL_STATUSES, ResearchResultCollector;
12680
+ var init_collector = __esm({
12681
+ "src/research/collector.ts"() {
12682
+ "use strict";
12683
+ init_cost();
12684
+ EMPTY_USAGE = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
12685
+ TERMINAL_STATUSES = /* @__PURE__ */ new Set([
12686
+ "completed",
12687
+ "failed",
12688
+ "cancelled",
12689
+ "incomplete",
12690
+ "budget_exceeded"
12691
+ ]);
12692
+ ResearchResultCollector = class {
12693
+ constructor(spec, now = Date.now) {
12694
+ this.spec = spec;
12695
+ this.now = now;
12696
+ }
12697
+ spec;
12698
+ now;
12699
+ reportParts = [];
12700
+ citations = /* @__PURE__ */ new Map();
12701
+ usage;
12702
+ lastStatus;
12703
+ doneReport = "";
12704
+ doneRaw;
12705
+ hasDone = false;
12706
+ firstEventAt;
12707
+ terminalAt;
12708
+ /**
12709
+ * Terminal error info from an `error` event.
12710
+ * @internal Read by tests only — not part of the public consumer contract.
12711
+ */
12712
+ terminalError;
12713
+ ingest(event) {
12714
+ this.firstEventAt ??= this.now();
12715
+ switch (event.type) {
12716
+ case "text":
12717
+ this.reportParts.push(event.delta);
12718
+ break;
12719
+ case "citation":
12720
+ this.addCitation(event.citation);
12721
+ break;
12722
+ case "usage":
12723
+ this.mergeUsage(event.usage);
12724
+ break;
12725
+ case "status":
12726
+ this.lastStatus = event.status;
12727
+ break;
12728
+ case "error":
12729
+ this.terminalError = event.error;
12730
+ this.terminalAt = this.now();
12731
+ break;
12732
+ case "done": {
12733
+ this.hasDone = true;
12734
+ this.lastStatus = event.result.status;
12735
+ this.doneReport = event.result.report;
12736
+ this.doneRaw = event.result.raw;
12737
+ for (const citation of event.result.citations ?? []) {
12738
+ this.addCitation(citation);
12739
+ }
12740
+ if (event.result.usage) {
12741
+ this.mergeUsage(event.result.usage);
12742
+ }
12743
+ this.terminalAt = this.now();
12744
+ break;
12745
+ }
12746
+ default:
12747
+ break;
12748
+ }
12749
+ }
12750
+ toResult(context) {
12751
+ const usage = { ...this.usage ?? EMPTY_USAGE };
12752
+ if (usage.costUSD === void 0 && this.spec) {
12753
+ usage.costUSD = estimateResearchCost(this.spec.pricing, usage);
12754
+ }
12755
+ const status = this.lastStatus ?? (this.terminalError ? "failed" : "in_progress");
12756
+ const lastIsTerminal = this.lastStatus !== void 0 && TERMINAL_STATUSES.has(this.lastStatus);
12757
+ return {
12758
+ jobId: context.jobId,
12759
+ provider: context.provider,
12760
+ model: context.model,
12761
+ // An error fails the run only when no explicit terminal status preceded
12762
+ // it (a timeout's "incomplete" is a partial, not a failure).
12763
+ status: this.terminalError && !this.hasDone && !lastIsTerminal ? "failed" : status,
12764
+ report: this.doneReport !== "" ? this.doneReport : this.reportParts.join(""),
12765
+ citations: [...this.citations.values()],
12766
+ usage,
12767
+ durationMs: this.firstEventAt !== void 0 && this.terminalAt !== void 0 ? this.terminalAt - this.firstEventAt : void 0,
12768
+ raw: this.doneRaw
12769
+ };
12770
+ }
12771
+ /** Whether a terminal event (`done` or `error`) was ingested. */
12772
+ get isTerminal() {
12773
+ return this.hasDone || this.terminalError !== void 0;
12774
+ }
12775
+ addCitation(citation) {
12776
+ const key = citationKey2(citation);
12777
+ if (!this.citations.has(key)) {
12778
+ this.citations.set(key, citation);
12779
+ }
12780
+ }
12781
+ mergeUsage(incoming) {
12782
+ const current = this.usage ?? { ...EMPTY_USAGE };
12783
+ this.usage = {
12784
+ ...current,
12785
+ ...incoming,
12786
+ // Cumulative counters may be re-reported; never let them regress.
12787
+ searches: max(current.searches, incoming.searches)
12788
+ };
12789
+ }
12790
+ };
12791
+ }
12792
+ });
12793
+
12794
+ // src/research/job.ts
12795
+ var ResearchJobImpl;
12796
+ var init_job = __esm({
12797
+ "src/research/job.ts"() {
12798
+ "use strict";
12799
+ init_errors2();
12800
+ init_collector();
12801
+ init_constants3();
12802
+ init_errors3();
12803
+ ResearchJobImpl = class {
12804
+ adapter;
12805
+ descriptor;
12806
+ spec;
12807
+ options;
12808
+ resumeFrom;
12809
+ collector;
12810
+ controller = new AbortController();
12811
+ timeoutMs;
12812
+ state = "idle";
12813
+ currentJobId;
12814
+ cursor;
12815
+ startedAt;
12816
+ timedOut = false;
12817
+ cancelRequested = false;
12818
+ finalResult;
12819
+ failure;
12820
+ completion;
12821
+ constructor(init) {
12822
+ this.adapter = init.adapter;
12823
+ this.descriptor = init.descriptor;
12824
+ this.spec = init.spec;
12825
+ this.options = init.options;
12826
+ this.resumeFrom = init.resumeFrom;
12827
+ this.collector = new ResearchResultCollector(init.spec);
12828
+ this.currentJobId = init.resumeFrom?.jobId ?? null;
12829
+ this.cursor = init.resumeFrom?.cursor;
12830
+ this.startedAt = init.resumeFrom?.startedAt;
12831
+ const specCap = init.spec?.maxDurationMs ?? Number.POSITIVE_INFINITY;
12832
+ this.timeoutMs = init.options?.timeoutMs ?? Math.min(RESEARCH_DEFAULT_TIMEOUT_MS, specCap);
12833
+ if (init.options?.signal) {
12834
+ const external = init.options.signal;
12835
+ if (external.aborted) {
12836
+ this.controller.abort(external.reason);
12837
+ } else {
12838
+ external.addEventListener("abort", () => this.controller.abort(external.reason), {
12839
+ once: true
12840
+ });
12841
+ }
12842
+ }
12843
+ }
12844
+ get jobId() {
12845
+ return this.currentJobId;
12846
+ }
12847
+ get provider() {
12848
+ return this.adapter.providerId;
12849
+ }
12850
+ get model() {
12851
+ return this.descriptor.name;
12852
+ }
12853
+ [Symbol.asyncIterator]() {
12854
+ return this.events()[Symbol.asyncIterator]();
12855
+ }
12856
+ events() {
12857
+ if (this.state !== "idle") {
12858
+ throw new ResearchStreamConsumedError();
12859
+ }
12860
+ this.state = "streaming";
12861
+ let resolveCompletion = () => {
12862
+ };
12863
+ const promise = new Promise((resolve2) => {
12864
+ resolveCompletion = resolve2;
12865
+ });
12866
+ this.completion = { promise, resolve: resolveCompletion };
12867
+ return this.run();
12868
+ }
12869
+ async result() {
12870
+ if (this.state === "idle") {
12871
+ for await (const _event of this.events()) {
12872
+ }
12873
+ } else if (this.state === "streaming" && this.completion) {
12874
+ await this.completion.promise;
12875
+ }
12876
+ if (this.failure) {
12877
+ throw this.failure;
12878
+ }
12879
+ this.finalResult ??= this.collector.toResult(this.resultContext());
12880
+ return this.finalResult;
12881
+ }
12882
+ async status() {
12883
+ if (!this.adapter.getResearchStatus) {
12884
+ throw new ResearchNotPollableError(
12885
+ `Provider "${this.provider}" does not support research status polling.`
12886
+ );
12887
+ }
12888
+ if (this.currentJobId === null) {
12889
+ throw new ResearchNotPollableError(
12890
+ `Research run on "${this.provider}" has no server-side job id to poll.`
12891
+ );
12892
+ }
12893
+ const snapshot = await this.adapter.getResearchStatus(this.buildRef());
12894
+ return snapshot.status;
12895
+ }
12896
+ async cancel() {
12897
+ this.cancelRequested = true;
12898
+ if (this.adapter.cancelResearch && this.currentJobId !== null) {
12899
+ await this.adapter.cancelResearch(this.buildRef());
12900
+ }
12901
+ this.controller.abort();
12902
+ }
12903
+ toRef() {
12904
+ if (this.currentJobId === null) {
12905
+ throw new ResearchJobNotResumableError(
12906
+ `Research run on "${this.provider}" has no server-side job id \u2014 the provider does not support background jobs, so it cannot be re-attached.`
12907
+ );
12908
+ }
12909
+ return this.buildRef();
12910
+ }
12911
+ buildRef() {
12912
+ if (this.currentJobId === null) {
12913
+ throw new ResearchJobNotResumableError("Research job has no server-side job id.");
12914
+ }
12915
+ return {
12916
+ provider: this.provider,
12917
+ model: this.descriptor.name,
12918
+ jobId: this.currentJobId,
12919
+ cursor: this.cursor,
12920
+ startedAt: this.startedAt
12921
+ };
12922
+ }
12923
+ resultContext() {
12924
+ return { provider: this.provider, model: this.descriptor.name, jobId: this.currentJobId };
12925
+ }
12926
+ openStream(resume) {
12927
+ if (resume) {
12928
+ if (!this.adapter.resumeResearch) {
12929
+ throw new ResearchJobNotResumableError(
12930
+ `Provider "${this.provider}" does not support resuming research jobs.`
12931
+ );
12932
+ }
12933
+ return this.adapter.resumeResearch(this.buildRef(), this.controller.signal);
12934
+ }
12935
+ if (!this.adapter.startResearch || !this.options) {
12936
+ throw new ResearchJobNotResumableError(
12937
+ `Provider "${this.provider}" cannot start this research job.`
12938
+ );
12939
+ }
12940
+ return this.adapter.startResearch(
12941
+ { ...this.options, signal: this.controller.signal },
12942
+ this.descriptor,
12943
+ this.spec
12944
+ );
12945
+ }
12946
+ observe(event) {
12947
+ if (event.cursor !== void 0) {
12948
+ this.cursor = event.cursor;
12949
+ }
12950
+ if (event.type === "created") {
12951
+ if (event.jobId !== null) {
12952
+ this.currentJobId = event.jobId;
12953
+ }
12954
+ this.startedAt ??= (/* @__PURE__ */ new Date()).toISOString();
12955
+ }
12956
+ this.collector.ingest(event);
12957
+ }
12958
+ canResume() {
12959
+ if (this.adapter.resumeResearch === void 0 || this.currentJobId === null) {
12960
+ return false;
12961
+ }
12962
+ return this.spec ? this.spec.capabilities.resumable : true;
12963
+ }
12964
+ async *run() {
12965
+ const timer = setTimeout(() => {
12966
+ this.timedOut = true;
12967
+ this.controller.abort(new ResearchTimeoutError(this.timeoutMs));
12968
+ }, this.timeoutMs);
12969
+ timer.unref?.();
12970
+ let reconnectAttempts = 0;
12971
+ let source = this.openStream(this.resumeFrom !== void 0);
12972
+ try {
12973
+ while (true) {
12974
+ const iterator = source[Symbol.asyncIterator]();
12975
+ try {
12976
+ while (true) {
12977
+ const next = await iterator.next();
12978
+ if (next.done) {
12979
+ return;
12980
+ }
12981
+ reconnectAttempts = 0;
12982
+ this.observe(next.value);
12983
+ yield next.value;
12984
+ if (this.collector.isTerminal) {
12985
+ return;
12986
+ }
12987
+ }
12988
+ } catch (error) {
12989
+ const err = error instanceof Error ? error : new Error(String(error));
12990
+ if (this.timedOut) {
12991
+ const statusEvent = { type: "status", status: "incomplete" };
12992
+ this.collector.ingest(statusEvent);
12993
+ yield statusEvent;
12994
+ const info2 = {
12995
+ message: new ResearchTimeoutError(this.timeoutMs).message,
12996
+ code: "timeout",
12997
+ retryable: false
12998
+ };
12999
+ this.collector.ingest({ type: "error", error: info2 });
13000
+ yield { type: "error", error: info2 };
13001
+ return;
13002
+ }
13003
+ if (this.cancelRequested && isAbortError(err)) {
13004
+ const event = { type: "status", status: "cancelled" };
13005
+ this.observe(event);
13006
+ yield event;
13007
+ return;
13008
+ }
13009
+ if (isAbortError(err)) {
13010
+ this.failure = err;
13011
+ throw err;
13012
+ }
13013
+ if (this.canResume() && reconnectAttempts < RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS) {
13014
+ reconnectAttempts += 1;
13015
+ source = this.openStream(true);
13016
+ continue;
13017
+ }
13018
+ const info = { message: err.message, retryable: false };
13019
+ this.collector.ingest({ type: "error", error: info });
13020
+ yield { type: "error", error: info };
13021
+ return;
13022
+ }
13023
+ }
13024
+ } finally {
13025
+ clearTimeout(timer);
13026
+ this.state = "finished";
13027
+ this.finalResult = this.collector.toResult(this.resultContext());
13028
+ this.completion?.resolve();
13029
+ }
13030
+ }
13031
+ };
13032
+ }
13033
+ });
13034
+
13035
+ // src/research/types.ts
13036
+ var RESEARCH_DATA_SOURCE_TOOL_TYPES;
13037
+ var init_types = __esm({
13038
+ "src/research/types.ts"() {
13039
+ "use strict";
13040
+ RESEARCH_DATA_SOURCE_TOOL_TYPES = [
13041
+ "web_search",
13042
+ "file_search",
13043
+ "mcp"
13044
+ ];
13045
+ }
13046
+ });
13047
+
13048
+ // src/research/namespace.ts
13049
+ var ResearchNamespace;
13050
+ var init_namespace = __esm({
13051
+ "src/research/namespace.ts"() {
13052
+ "use strict";
13053
+ init_logger();
13054
+ init_constants3();
13055
+ init_errors3();
13056
+ init_job();
13057
+ init_types();
13058
+ ResearchNamespace = class {
13059
+ constructor(adapters, parser, now = Date.now, logger2) {
13060
+ this.adapters = adapters;
13061
+ this.parser = parser;
13062
+ this.now = now;
13063
+ this.logger = logger2 ?? createLogger({ name: "llmist:research" });
13064
+ }
13065
+ adapters;
13066
+ parser;
13067
+ now;
13068
+ logger;
13069
+ /**
13070
+ * Start a research run. Returns immediately; the provider stream opens
13071
+ * lazily on first iteration (or on `result()`).
13072
+ */
13073
+ start(options) {
13074
+ const descriptor = this.parser.parse(options.model);
13075
+ const adapter = this.findResearchAdapter(descriptor);
13076
+ if (!adapter) {
13077
+ throw new ResearchNotSupportedError(
13078
+ `No provider supports deep research for model "${options.model}". Research-capable models: ${this.describeAvailableModels()}`
13079
+ );
13080
+ }
13081
+ const spec = this.findSpec(adapter, descriptor.name);
13082
+ const validated = spec ? this.validate(options, spec) : options;
13083
+ return new ResearchJobImpl({ adapter, descriptor, spec, options: validated });
13084
+ }
13085
+ /**
13086
+ * Re-attach to a background research job from a serialized ref.
13087
+ * No network happens until the returned job is iterated.
13088
+ */
13089
+ attach(ref) {
13090
+ const adapter = this.findAdapterByProviderId(ref.provider);
13091
+ if (!adapter) {
13092
+ throw new ResearchNotSupportedError(
13093
+ `No registered provider with id "${ref.provider}" to attach research job "${ref.jobId}".`
13094
+ );
13095
+ }
13096
+ if (!adapter.resumeResearch) {
13097
+ throw new ResearchJobNotResumableError(
13098
+ `Provider "${ref.provider}" does not support resuming research jobs.`
13099
+ );
13100
+ }
13101
+ const descriptor = { provider: ref.provider, name: ref.model };
13102
+ const spec = this.findSpec(adapter, ref.model);
13103
+ return new ResearchJobImpl({ adapter, descriptor, spec, resumeFrom: ref });
13104
+ }
13105
+ /** One-shot status poll for a job ref. */
13106
+ async get(ref) {
13107
+ const adapter = this.findAdapterByProviderId(ref.provider);
13108
+ if (!adapter) {
13109
+ throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
13110
+ }
13111
+ if (!adapter.getResearchStatus) {
13112
+ throw new ResearchNotPollableError(
13113
+ `Provider "${ref.provider}" does not support research status polling.`
13114
+ );
13115
+ }
13116
+ return adapter.getResearchStatus(ref);
13117
+ }
13118
+ /** Cancel a background job server-side. */
13119
+ async cancel(ref) {
13120
+ const adapter = this.findAdapterByProviderId(ref.provider);
13121
+ if (!adapter) {
13122
+ throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
13123
+ }
13124
+ if (!adapter.cancelResearch) {
13125
+ throw new ResearchNotSupportedError(
13126
+ `Provider "${ref.provider}" does not support cancelling research jobs.`
13127
+ );
13128
+ }
13129
+ return adapter.cancelResearch(ref);
13130
+ }
13131
+ /** All research-capable models/agents across registered providers. */
13132
+ listModels() {
13133
+ const specs = [];
13134
+ for (const adapter of this.adapters) {
13135
+ specs.push(...adapter.getResearchModelSpecs?.() ?? []);
13136
+ }
13137
+ return specs;
13138
+ }
13139
+ /** Whether any registered provider supports research for this model. */
13140
+ supportsModel(model) {
13141
+ try {
13142
+ return this.findResearchAdapter(this.parser.parse(model)) !== void 0;
13143
+ } catch {
13144
+ return false;
13145
+ }
13146
+ }
13147
+ findResearchAdapter(descriptor) {
13148
+ return this.adapters.find(
13149
+ (adapter) => adapter.supports(descriptor) && (adapter.supportsResearch?.(descriptor.name) ?? false)
13150
+ );
13151
+ }
13152
+ findAdapterByProviderId(providerId) {
13153
+ return this.adapters.find((adapter) => adapter.providerId === providerId);
13154
+ }
13155
+ findSpec(adapter, modelId) {
13156
+ return adapter.getResearchModelSpecs?.().find((spec) => spec.modelId === modelId);
13157
+ }
13158
+ describeAvailableModels() {
13159
+ const models = this.listModels();
13160
+ if (models.length === 0) {
13161
+ return "(none registered)";
13162
+ }
13163
+ return models.map((spec) => `${spec.provider}:${spec.modelId}`).join(", ");
13164
+ }
13165
+ /**
13166
+ * Pre-flight validation against the catalog spec — fails fast before any
13167
+ * network call and applies spec-driven defaults.
13168
+ */
13169
+ validate(options, spec) {
13170
+ this.enforceLifecycle(spec);
13171
+ const background = options.background ?? spec.capabilities.background;
13172
+ if (background && !spec.capabilities.background) {
13173
+ throw new ResearchValidationError(
13174
+ `Model "${spec.modelId}" does not support background research jobs.`
13175
+ );
13176
+ }
13177
+ if (options.previousJobId && !spec.capabilities.followUps) {
13178
+ throw new ResearchValidationError(
13179
+ `Model "${spec.modelId}" does not support follow-up research runs (previousJobId).`
13180
+ );
13181
+ }
13182
+ const tools = this.resolveTools(options, spec);
13183
+ return { ...options, background, tools };
13184
+ }
13185
+ resolveTools(options, spec) {
13186
+ if (options.tools === void 0) {
13187
+ return spec.requiredTools;
13188
+ }
13189
+ for (const tool of options.tools) {
13190
+ if (!spec.capabilities.tools.includes(tool.type)) {
13191
+ throw new ResearchValidationError(
13192
+ `Model "${spec.modelId}" does not accept the "${tool.type}" tool. ` + (spec.capabilities.tools.length > 0 ? `Accepted tools: ${spec.capabilities.tools.join(", ")}.` : "This model manages its research tools itself \u2014 omit the tools option.")
13193
+ );
13194
+ }
13195
+ }
13196
+ const hasDataSource = options.tools.some(
13197
+ (tool) => RESEARCH_DATA_SOURCE_TOOL_TYPES.includes(tool.type)
13198
+ );
13199
+ if (!hasDataSource && spec.requiredTools) {
13200
+ return [...spec.requiredTools, ...options.tools];
13201
+ }
13202
+ return options.tools;
13203
+ }
13204
+ enforceLifecycle(spec) {
13205
+ const shutdownDate = spec.metadata?.shutdownDate;
13206
+ if (!shutdownDate) {
13207
+ return;
13208
+ }
13209
+ const shutdownMs = Date.parse(shutdownDate);
13210
+ if (Number.isNaN(shutdownMs)) {
13211
+ return;
13212
+ }
13213
+ const nowMs = this.now();
13214
+ if (nowMs >= shutdownMs) {
13215
+ throw new ResearchDeprecatedModelError({
13216
+ modelId: spec.modelId,
13217
+ shutdownDate,
13218
+ replacement: spec.metadata?.replacement
13219
+ });
13220
+ }
13221
+ const warningStartMs = shutdownMs - RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS * MS_PER_DAY;
13222
+ if (nowMs >= warningStartMs) {
13223
+ this.logger.warn(
13224
+ `Research model "${spec.modelId}" shuts down on ${shutdownDate}` + (spec.metadata?.replacement ? ` \u2014 migrate to "${spec.metadata.replacement}".` : ".")
13225
+ );
13226
+ }
13227
+ }
13228
+ };
13229
+ }
13230
+ });
13231
+
11161
13232
  // src/core/model-registry.ts
11162
13233
  var ModelRegistry;
11163
13234
  var init_model_registry = __esm({
@@ -11382,6 +13453,8 @@ var init_image = __esm({
11382
13453
  this.adapters = adapters;
11383
13454
  this.defaultProvider = defaultProvider;
11384
13455
  }
13456
+ adapters;
13457
+ defaultProvider;
11385
13458
  /**
11386
13459
  * Generate images from a text prompt.
11387
13460
  *
@@ -11434,6 +13507,8 @@ var init_speech = __esm({
11434
13507
  this.adapters = adapters;
11435
13508
  this.defaultProvider = defaultProvider;
11436
13509
  }
13510
+ adapters;
13511
+ defaultProvider;
11437
13512
  /**
11438
13513
  * Generate speech audio from text.
11439
13514
  *
@@ -11529,6 +13604,7 @@ var init_text = __esm({
11529
13604
  constructor(client) {
11530
13605
  this.client = client;
11531
13606
  }
13607
+ client;
11532
13608
  /**
11533
13609
  * Generate a complete text response.
11534
13610
  *
@@ -11564,6 +13640,7 @@ var init_vision = __esm({
11564
13640
  constructor(client) {
11565
13641
  this.client = client;
11566
13642
  }
13643
+ client;
11567
13644
  /**
11568
13645
  * Build a message builder with the image content attached.
11569
13646
  * Handles URLs, data URLs, base64 strings, and binary buffers.
@@ -11690,6 +13767,7 @@ var init_options = __esm({
11690
13767
  constructor(defaultProvider = "openai") {
11691
13768
  this.defaultProvider = defaultProvider;
11692
13769
  }
13770
+ defaultProvider;
11693
13771
  parse(identifier) {
11694
13772
  const trimmed = identifier.trim();
11695
13773
  if (!trimmed) {
@@ -11721,6 +13799,7 @@ var init_client2 = __esm({
11721
13799
  "use strict";
11722
13800
  init_builder();
11723
13801
  init_discovery();
13802
+ init_namespace();
11724
13803
  init_constants();
11725
13804
  init_model_registry();
11726
13805
  init_image();
@@ -11739,6 +13818,12 @@ var init_client2 = __esm({
11739
13818
  image;
11740
13819
  speech;
11741
13820
  vision;
13821
+ /**
13822
+ * Deep research — long-running, server-side research jobs with cited
13823
+ * reports (OpenAI Responses, Gemini Interactions, OpenRouter research
13824
+ * models). See docs/specs/002-deep-research.md.
13825
+ */
13826
+ research;
11742
13827
  constructor(...args) {
11743
13828
  let adapters = [];
11744
13829
  let defaultProvider;
@@ -11790,6 +13875,7 @@ var init_client2 = __esm({
11790
13875
  this.image = new ImageNamespace(this.adapters, this.defaultProvider);
11791
13876
  this.speech = new SpeechNamespace(this.adapters, this.defaultProvider);
11792
13877
  this.vision = new VisionNamespace(this);
13878
+ this.research = new ResearchNamespace(this.adapters, this.parser);
11793
13879
  }
11794
13880
  stream(options) {
11795
13881
  const descriptor = this.parser.parse(options.model);
@@ -12056,8 +14142,8 @@ var init_builder = __esm({
12056
14142
  return this;
12057
14143
  }
12058
14144
  /** Set maximum iterations. */
12059
- withMaxIterations(max) {
12060
- this.core.maxIterations = max;
14145
+ withMaxIterations(max2) {
14146
+ this.core.maxIterations = max2;
12061
14147
  return this;
12062
14148
  }
12063
14149
  /** Set the budget limit in USD. */
@@ -12186,10 +14272,10 @@ var init_builder = __esm({
12186
14272
  return this;
12187
14273
  }
12188
14274
  /** Set the maximum number of gadgets to execute per LLM response. */
12189
- withMaxGadgetsPerResponse(max) {
12190
- if (max < 0) throw new Error("maxGadgetsPerResponse must be a non-negative number");
12191
- if (!Number.isInteger(max)) throw new Error("maxGadgetsPerResponse must be an integer");
12192
- 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;
12193
14279
  return this;
12194
14280
  }
12195
14281
  /** Enable or disable gadget output limiting. */
@@ -12890,6 +14976,8 @@ var init_cost_reporting_client = __esm({
12890
14976
  }
12891
14977
  };
12892
14978
  }
14979
+ client;
14980
+ reportCost;
12893
14981
  image;
12894
14982
  speech;
12895
14983
  /**
@@ -16046,7 +18134,7 @@ var init_agent = __esm({
16046
18134
  }
16047
18135
  const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
16048
18136
  if (this.mcpSpecs.length > 0) {
16049
- const { setupMcpServers } = await import("./runtime-KB7US2FQ.js");
18137
+ const { setupMcpServers } = await import("./runtime-IUKW77QX.js");
16050
18138
  this.mcpLifecycle = await setupMcpServers({
16051
18139
  specs: this.mcpSpecs,
16052
18140
  registry: this.registry,
@@ -16871,6 +18959,14 @@ init_tool_adapter();
16871
18959
  // src/index.ts
16872
18960
  init_constants2();
16873
18961
 
18962
+ // src/research/index.ts
18963
+ init_collector();
18964
+ init_constants3();
18965
+ init_cost();
18966
+ init_errors3();
18967
+ init_namespace();
18968
+ init_types();
18969
+
16874
18970
  // src/utils/config-resolver.ts
16875
18971
  function resolveValue(ctx, gadgetName, options) {
16876
18972
  const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
@@ -17169,11 +19265,11 @@ var format = {
17169
19265
  };
17170
19266
 
17171
19267
  // src/utils/timing.ts
17172
- function randomDelay(min, max) {
17173
- return Math.floor(Math.random() * (max - min + 1)) + min;
19268
+ function randomDelay(min, max2) {
19269
+ return Math.floor(Math.random() * (max2 - min + 1)) + min;
17174
19270
  }
17175
- async function humanDelay(min = 50, max = 150) {
17176
- const delay = randomDelay(min, max);
19271
+ async function humanDelay(min = 50, max2 = 150) {
19272
+ const delay = randomDelay(min, max2);
17177
19273
  return new Promise((resolve2) => setTimeout(resolve2, delay));
17178
19274
  }
17179
19275
  async function withTimeout(fn, timeoutMs, signal) {
@@ -17312,7 +19408,17 @@ export {
17312
19408
  OpenAIChatProvider,
17313
19409
  OpenAICompatibleProvider,
17314
19410
  OpenRouterProvider,
19411
+ RESEARCH_DATA_SOURCE_TOOL_TYPES,
17315
19412
  RateLimitTracker,
19413
+ ResearchDeprecatedModelError,
19414
+ ResearchJobNotResumableError,
19415
+ ResearchNamespace,
19416
+ ResearchNotPollableError,
19417
+ ResearchNotSupportedError,
19418
+ ResearchResultCollector,
19419
+ ResearchStreamConsumedError,
19420
+ ResearchTimeoutError,
19421
+ ResearchValidationError,
17316
19422
  SimpleSessionManager,
17317
19423
  Skill,
17318
19424
  SkillRegistry,
@@ -17346,6 +19452,7 @@ export {
17346
19452
  detectImageMimeType,
17347
19453
  discoverProviderAdapters,
17348
19454
  discoverSkills,
19455
+ estimateResearchCost,
17349
19456
  extractMessageText,
17350
19457
  extractRetryAfterMs,
17351
19458
  filterByDepth,