llmist 18.2.0 → 18.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-DNB4DPM4.js → chunk-I4KQ7U7F.js} +23 -4
- package/dist/chunk-I4KQ7U7F.js.map +1 -0
- package/dist/index.cjs +2568 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +711 -2
- package/dist/index.d.ts +711 -2
- package/dist/index.js +2390 -84
- package/dist/index.js.map +1 -1
- package/dist/{runtime-KB7US2FQ.js → runtime-IUKW77QX.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-DNB4DPM4.js.map +0 -1
- /package/dist/{runtime-KB7US2FQ.js.map → runtime-IUKW77QX.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,8 @@ var init_execution_tree_aggregator = __esm({
|
|
|
40
40
|
this.nodes = nodes;
|
|
41
41
|
this.getDescendants = getDescendants;
|
|
42
42
|
}
|
|
43
|
+
nodes;
|
|
44
|
+
getDescendants;
|
|
43
45
|
// ===========================================================================
|
|
44
46
|
// Private helpers
|
|
45
47
|
// ===========================================================================
|
|
@@ -2516,21 +2518,39 @@ function createLogger(options = {}) {
|
|
|
2516
2518
|
prettyLogTemplate: LOG_TEMPLATE,
|
|
2517
2519
|
// Use overwrite to redirect tslog's formatted output to file instead of console
|
|
2518
2520
|
overwrite: useFileLogging ? {
|
|
2519
|
-
transportFormatted: (logMetaMarkup, logArgs,
|
|
2521
|
+
transportFormatted: (logMetaMarkup, logArgs, logErrors) => {
|
|
2520
2522
|
const args = logArgs.map(
|
|
2521
2523
|
(arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
2522
2524
|
);
|
|
2523
2525
|
if (sharedLogFileStream) {
|
|
2524
2526
|
const meta = stripAnsi(logMetaMarkup);
|
|
2525
2527
|
const fileArgs = args.map((a) => stripAnsi(a));
|
|
2526
|
-
|
|
2528
|
+
const errTail = logErrors.length ? `
|
|
2529
|
+
${logErrors.map((e) => stripAnsi(e)).join("\n")}` : "";
|
|
2530
|
+
sharedLogFileStream.write(`${meta}${fileArgs.join(" ")}${errTail}
|
|
2527
2531
|
`);
|
|
2528
2532
|
}
|
|
2529
2533
|
if (teeToConsole) {
|
|
2530
|
-
|
|
2534
|
+
const errTail = logErrors.length ? `
|
|
2535
|
+
${logErrors.join("\n")}` : "";
|
|
2536
|
+
process.stdout.write(`${logMetaMarkup}${args.join(" ")}${errTail}
|
|
2531
2537
|
`);
|
|
2532
2538
|
}
|
|
2533
2539
|
}
|
|
2540
|
+
} : defaultType === "pretty" ? {
|
|
2541
|
+
// Diagnostics go to stderr (POSIX convention). tslog's default
|
|
2542
|
+
// pretty transport uses console.log, which pollutes stdout —
|
|
2543
|
+
// breaking consumers whose stdout is content or machine-readable
|
|
2544
|
+
// (piped CLI output, --json/NDJSON modes, --background job refs).
|
|
2545
|
+
transportFormatted: (logMetaMarkup, logArgs, logErrors) => {
|
|
2546
|
+
const args = logArgs.map(
|
|
2547
|
+
(arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
2548
|
+
);
|
|
2549
|
+
const errTail = logErrors.length ? `
|
|
2550
|
+
${logErrors.join("\n")}` : "";
|
|
2551
|
+
process.stderr.write(`${logMetaMarkup}${args.join(" ")}${errTail}
|
|
2552
|
+
`);
|
|
2553
|
+
}
|
|
2534
2554
|
} : void 0
|
|
2535
2555
|
});
|
|
2536
2556
|
return logger2;
|
|
@@ -3258,6 +3278,18 @@ async function runWithHandlers(agentGenerator, handlers) {
|
|
|
3258
3278
|
});
|
|
3259
3279
|
}
|
|
3260
3280
|
break;
|
|
3281
|
+
case "gadget_args_partial":
|
|
3282
|
+
if (handlers.onGadgetArgsPartial) {
|
|
3283
|
+
await handlers.onGadgetArgsPartial({
|
|
3284
|
+
gadgetName: event.gadgetName,
|
|
3285
|
+
invocationId: event.invocationId,
|
|
3286
|
+
fieldPath: event.fieldPath,
|
|
3287
|
+
value: event.value,
|
|
3288
|
+
delta: event.delta,
|
|
3289
|
+
isFieldComplete: event.isFieldComplete
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
break;
|
|
3261
3293
|
case "gadget_result":
|
|
3262
3294
|
if (handlers.onGadgetResult) {
|
|
3263
3295
|
await handlers.onGadgetResult(event.result);
|
|
@@ -5039,7 +5071,7 @@ var init_gadget_output_store = __esm({
|
|
|
5039
5071
|
charCount: content.length,
|
|
5040
5072
|
byteSize: encoder.encode(content).length,
|
|
5041
5073
|
lineCount: lines.length,
|
|
5042
|
-
maxLineLength: lines.reduce((
|
|
5074
|
+
maxLineLength: lines.reduce((max2, line) => Math.max(max2, line.length), 0),
|
|
5043
5075
|
timestamp: /* @__PURE__ */ new Date()
|
|
5044
5076
|
};
|
|
5045
5077
|
this.outputs.set(id, stored);
|
|
@@ -7690,6 +7722,7 @@ var init_base_provider = __esm({
|
|
|
7690
7722
|
constructor(client) {
|
|
7691
7723
|
this.client = client;
|
|
7692
7724
|
}
|
|
7725
|
+
client;
|
|
7693
7726
|
/**
|
|
7694
7727
|
* Template method that defines the skeleton of the streaming algorithm.
|
|
7695
7728
|
* This orchestrates the four-step process without dictating provider-specific details.
|
|
@@ -8059,6 +8092,7 @@ var init_gemini_cache_manager = __esm({
|
|
|
8059
8092
|
constructor(client) {
|
|
8060
8093
|
this.client = client;
|
|
8061
8094
|
}
|
|
8095
|
+
client;
|
|
8062
8096
|
activeCache = null;
|
|
8063
8097
|
/**
|
|
8064
8098
|
* Get or create a cache for the given content.
|
|
@@ -8601,6 +8635,621 @@ var init_gemini_models = __esm({
|
|
|
8601
8635
|
}
|
|
8602
8636
|
});
|
|
8603
8637
|
|
|
8638
|
+
// src/research/constants.ts
|
|
8639
|
+
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;
|
|
8640
|
+
var init_constants3 = __esm({
|
|
8641
|
+
"src/research/constants.ts"() {
|
|
8642
|
+
"use strict";
|
|
8643
|
+
RESEARCH_DEFAULT_TIMEOUT_MS = 36e5;
|
|
8644
|
+
GEMINI_RESEARCH_MAX_DURATION_MS = 36e5;
|
|
8645
|
+
OPENAI_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
|
|
8646
|
+
OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS = 36e5;
|
|
8647
|
+
RESEARCH_POLL_INTERVAL_MS = 1e4;
|
|
8648
|
+
RESEARCH_POLL_MAX_INTERVAL_MS = 6e4;
|
|
8649
|
+
RESEARCH_POLL_BACKOFF_FACTOR = 1.5;
|
|
8650
|
+
RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS = 5;
|
|
8651
|
+
RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS = 30;
|
|
8652
|
+
RESEARCH_COST_DECIMALS = 6;
|
|
8653
|
+
TOKENS_PER_MILLION = 1e6;
|
|
8654
|
+
SEARCHES_PER_THOUSAND = 1e3;
|
|
8655
|
+
MS_PER_DAY = 864e5;
|
|
8656
|
+
}
|
|
8657
|
+
});
|
|
8658
|
+
|
|
8659
|
+
// src/research/errors.ts
|
|
8660
|
+
var ResearchNotSupportedError, ResearchJobNotResumableError, ResearchNotPollableError, ResearchTimeoutError, ResearchDeprecatedModelError, ResearchValidationError, ResearchStreamConsumedError;
|
|
8661
|
+
var init_errors2 = __esm({
|
|
8662
|
+
"src/research/errors.ts"() {
|
|
8663
|
+
"use strict";
|
|
8664
|
+
ResearchNotSupportedError = class extends Error {
|
|
8665
|
+
constructor(message) {
|
|
8666
|
+
super(message);
|
|
8667
|
+
this.name = "ResearchNotSupportedError";
|
|
8668
|
+
}
|
|
8669
|
+
};
|
|
8670
|
+
ResearchJobNotResumableError = class extends Error {
|
|
8671
|
+
constructor(message) {
|
|
8672
|
+
super(message);
|
|
8673
|
+
this.name = "ResearchJobNotResumableError";
|
|
8674
|
+
}
|
|
8675
|
+
};
|
|
8676
|
+
ResearchNotPollableError = class extends Error {
|
|
8677
|
+
constructor(message) {
|
|
8678
|
+
super(message);
|
|
8679
|
+
this.name = "ResearchNotPollableError";
|
|
8680
|
+
}
|
|
8681
|
+
};
|
|
8682
|
+
ResearchTimeoutError = class extends Error {
|
|
8683
|
+
timeoutMs;
|
|
8684
|
+
constructor(timeoutMs) {
|
|
8685
|
+
super(
|
|
8686
|
+
`Research run exceeded the client-side time budget of ${timeoutMs}ms. A background job keeps running server-side \u2014 re-attach via its ref.`
|
|
8687
|
+
);
|
|
8688
|
+
this.name = "ResearchTimeoutError";
|
|
8689
|
+
this.timeoutMs = timeoutMs;
|
|
8690
|
+
}
|
|
8691
|
+
};
|
|
8692
|
+
ResearchDeprecatedModelError = class extends Error {
|
|
8693
|
+
modelId;
|
|
8694
|
+
shutdownDate;
|
|
8695
|
+
replacement;
|
|
8696
|
+
constructor(params) {
|
|
8697
|
+
super(
|
|
8698
|
+
`Research model "${params.modelId}" was shut down by its provider on ${params.shutdownDate}.` + (params.replacement ? ` Use "${params.replacement}" instead.` : "")
|
|
8699
|
+
);
|
|
8700
|
+
this.name = "ResearchDeprecatedModelError";
|
|
8701
|
+
this.modelId = params.modelId;
|
|
8702
|
+
this.shutdownDate = params.shutdownDate;
|
|
8703
|
+
this.replacement = params.replacement;
|
|
8704
|
+
}
|
|
8705
|
+
};
|
|
8706
|
+
ResearchValidationError = class extends Error {
|
|
8707
|
+
constructor(message) {
|
|
8708
|
+
super(message);
|
|
8709
|
+
this.name = "ResearchValidationError";
|
|
8710
|
+
}
|
|
8711
|
+
};
|
|
8712
|
+
ResearchStreamConsumedError = class extends Error {
|
|
8713
|
+
constructor() {
|
|
8714
|
+
super(
|
|
8715
|
+
"This research job's event stream was already consumed. Iterate events() (or the job itself) once; use result() for the aggregated outcome."
|
|
8716
|
+
);
|
|
8717
|
+
this.name = "ResearchStreamConsumedError";
|
|
8718
|
+
}
|
|
8719
|
+
};
|
|
8720
|
+
}
|
|
8721
|
+
});
|
|
8722
|
+
|
|
8723
|
+
// src/providers/gemini-research.ts
|
|
8724
|
+
function buildGeminiResearchRequest(options, agentId) {
|
|
8725
|
+
if (options.background === false) {
|
|
8726
|
+
throw new ResearchValidationError(
|
|
8727
|
+
"Gemini deep research requires background execution \u2014 background: false is not supported."
|
|
8728
|
+
);
|
|
8729
|
+
}
|
|
8730
|
+
return {
|
|
8731
|
+
agent: agentId,
|
|
8732
|
+
input: options.query,
|
|
8733
|
+
background: true,
|
|
8734
|
+
store: true,
|
|
8735
|
+
agent_config: {
|
|
8736
|
+
type: "deep-research",
|
|
8737
|
+
thinking_summaries: options.reasoning?.includeThinking === false ? "none" : "auto"
|
|
8738
|
+
},
|
|
8739
|
+
...options.systemPrompt ? { system_instruction: options.systemPrompt } : {},
|
|
8740
|
+
...options.previousJobId ? { previous_interaction_id: options.previousJobId } : {},
|
|
8741
|
+
...options.extra ?? {}
|
|
8742
|
+
};
|
|
8743
|
+
}
|
|
8744
|
+
function mapInteractionStatus(status) {
|
|
8745
|
+
if (status && GEMINI_STATUS_VALUES.has(status)) {
|
|
8746
|
+
return status;
|
|
8747
|
+
}
|
|
8748
|
+
return "in_progress";
|
|
8749
|
+
}
|
|
8750
|
+
function annotationToCitation(annotation) {
|
|
8751
|
+
if (annotation.type !== "url_citation" || !annotation.url) return void 0;
|
|
8752
|
+
return {
|
|
8753
|
+
url: annotation.url,
|
|
8754
|
+
title: annotation.title,
|
|
8755
|
+
startIndex: annotation.start_index,
|
|
8756
|
+
endIndex: annotation.end_index
|
|
8757
|
+
};
|
|
8758
|
+
}
|
|
8759
|
+
function extractReportFromInteraction(interaction) {
|
|
8760
|
+
const parts = [];
|
|
8761
|
+
const citations = [];
|
|
8762
|
+
for (const step of interaction.steps ?? []) {
|
|
8763
|
+
if (step.type !== "model_output") continue;
|
|
8764
|
+
for (const content of step.content ?? []) {
|
|
8765
|
+
if (content.type !== "text") continue;
|
|
8766
|
+
parts.push(content.text);
|
|
8767
|
+
for (const annotation of content.annotations ?? []) {
|
|
8768
|
+
const citation = annotationToCitation(annotation);
|
|
8769
|
+
if (citation) citations.push(citation);
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
}
|
|
8773
|
+
return { report: parts.join(""), citations };
|
|
8774
|
+
}
|
|
8775
|
+
function countSearchesInSteps(interaction) {
|
|
8776
|
+
let searches = 0;
|
|
8777
|
+
for (const step of interaction.steps ?? []) {
|
|
8778
|
+
if (step.type === "google_search_call") {
|
|
8779
|
+
searches += step.arguments?.queries?.length ?? 1;
|
|
8780
|
+
}
|
|
8781
|
+
}
|
|
8782
|
+
return searches;
|
|
8783
|
+
}
|
|
8784
|
+
function usageFromInteraction(interaction, streamedSearches) {
|
|
8785
|
+
const usage = interaction.usage;
|
|
8786
|
+
const searches = streamedSearches ?? countSearchesInSteps(interaction);
|
|
8787
|
+
return {
|
|
8788
|
+
inputTokens: usage?.total_input_tokens ?? 0,
|
|
8789
|
+
outputTokens: usage?.total_output_tokens ?? 0,
|
|
8790
|
+
totalTokens: usage?.total_tokens ?? 0,
|
|
8791
|
+
cachedInputTokens: usage?.total_cached_tokens,
|
|
8792
|
+
reasoningTokens: usage?.total_thought_tokens,
|
|
8793
|
+
...searches > 0 ? { searches } : {}
|
|
8794
|
+
};
|
|
8795
|
+
}
|
|
8796
|
+
function doneEventFromInteraction(interaction, streamedSearches) {
|
|
8797
|
+
const { report, citations } = extractReportFromInteraction(interaction);
|
|
8798
|
+
return {
|
|
8799
|
+
type: "done",
|
|
8800
|
+
result: {
|
|
8801
|
+
status: mapInteractionStatus(interaction.status),
|
|
8802
|
+
report,
|
|
8803
|
+
citations,
|
|
8804
|
+
usage: usageFromInteraction(interaction, streamedSearches),
|
|
8805
|
+
raw: interaction
|
|
8806
|
+
}
|
|
8807
|
+
};
|
|
8808
|
+
}
|
|
8809
|
+
async function* normalizeInteractionsStream(stream2, fetchFinal) {
|
|
8810
|
+
let streamedSearches = 0;
|
|
8811
|
+
const countedSearchSteps = /* @__PURE__ */ new Set();
|
|
8812
|
+
let interactionId;
|
|
8813
|
+
for await (const event of stream2) {
|
|
8814
|
+
const cursor = event.event_id;
|
|
8815
|
+
switch (event.event_type) {
|
|
8816
|
+
case "interaction.created": {
|
|
8817
|
+
const interaction = event.interaction;
|
|
8818
|
+
interactionId = interaction.id;
|
|
8819
|
+
yield { type: "created", jobId: interaction.id, cursor, rawEvent: event };
|
|
8820
|
+
yield { type: "status", status: mapInteractionStatus(interaction.status), cursor };
|
|
8821
|
+
break;
|
|
8822
|
+
}
|
|
8823
|
+
case "interaction.status_update":
|
|
8824
|
+
yield {
|
|
8825
|
+
type: "status",
|
|
8826
|
+
status: mapInteractionStatus(event.status),
|
|
8827
|
+
cursor,
|
|
8828
|
+
rawEvent: event
|
|
8829
|
+
};
|
|
8830
|
+
break;
|
|
8831
|
+
case "step.start": {
|
|
8832
|
+
const step = event.step;
|
|
8833
|
+
if (step.type === "thought") {
|
|
8834
|
+
yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
|
|
8835
|
+
} else if (step.type === "model_output") {
|
|
8836
|
+
yield { type: "phase", phase: "writing", cursor, rawEvent: event };
|
|
8837
|
+
} else if (step.type === "google_search_call") {
|
|
8838
|
+
if (!countedSearchSteps.has(event.index)) {
|
|
8839
|
+
countedSearchSteps.add(event.index);
|
|
8840
|
+
streamedSearches += step.arguments?.queries?.length ?? 1;
|
|
8841
|
+
}
|
|
8842
|
+
yield { type: "phase", phase: "searching", cursor, rawEvent: event };
|
|
8843
|
+
yield {
|
|
8844
|
+
type: "search",
|
|
8845
|
+
action: "search",
|
|
8846
|
+
status: "started",
|
|
8847
|
+
query: step.arguments?.queries?.join("; "),
|
|
8848
|
+
cursor
|
|
8849
|
+
};
|
|
8850
|
+
} else if (step.type === "google_search_result") {
|
|
8851
|
+
yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
|
|
8852
|
+
}
|
|
8853
|
+
break;
|
|
8854
|
+
}
|
|
8855
|
+
case "step.delta":
|
|
8856
|
+
yield* normalizeDelta(event, cursor, (queries) => {
|
|
8857
|
+
if (!countedSearchSteps.has(event.index)) {
|
|
8858
|
+
countedSearchSteps.add(event.index);
|
|
8859
|
+
streamedSearches += queries;
|
|
8860
|
+
}
|
|
8861
|
+
});
|
|
8862
|
+
break;
|
|
8863
|
+
case "step.stop":
|
|
8864
|
+
break;
|
|
8865
|
+
case "interaction.completed": {
|
|
8866
|
+
let interaction = event.interaction;
|
|
8867
|
+
const finalId = interaction.id ?? interactionId;
|
|
8868
|
+
if (fetchFinal && finalId !== void 0 && !interaction.usage?.total_tokens) {
|
|
8869
|
+
try {
|
|
8870
|
+
interaction = await fetchFinal(finalId);
|
|
8871
|
+
} catch {
|
|
8872
|
+
}
|
|
8873
|
+
}
|
|
8874
|
+
const searches = streamedSearches > 0 ? streamedSearches : void 0;
|
|
8875
|
+
yield {
|
|
8876
|
+
type: "usage",
|
|
8877
|
+
usage: usageFromInteraction(interaction, searches),
|
|
8878
|
+
cursor,
|
|
8879
|
+
rawEvent: event
|
|
8880
|
+
};
|
|
8881
|
+
yield { ...doneEventFromInteraction(interaction, searches), cursor };
|
|
8882
|
+
break;
|
|
8883
|
+
}
|
|
8884
|
+
case "error":
|
|
8885
|
+
if (event.error?.code === "client_closed_request") break;
|
|
8886
|
+
yield {
|
|
8887
|
+
type: "error",
|
|
8888
|
+
error: {
|
|
8889
|
+
message: event.error?.message ?? "Gemini interaction error",
|
|
8890
|
+
code: event.error?.code !== void 0 ? String(event.error.code) : void 0,
|
|
8891
|
+
retryable: isRetryableError(new Error(event.error?.message ?? ""))
|
|
8892
|
+
},
|
|
8893
|
+
cursor,
|
|
8894
|
+
rawEvent: event
|
|
8895
|
+
};
|
|
8896
|
+
break;
|
|
8897
|
+
default: {
|
|
8898
|
+
const type = event.event_type ?? "";
|
|
8899
|
+
if (type.endsWith("error")) {
|
|
8900
|
+
const err = event.error;
|
|
8901
|
+
if (err?.code === "client_closed_request") break;
|
|
8902
|
+
yield {
|
|
8903
|
+
type: "error",
|
|
8904
|
+
error: { message: err?.message ?? "Gemini interaction error", retryable: false },
|
|
8905
|
+
cursor,
|
|
8906
|
+
rawEvent: event
|
|
8907
|
+
};
|
|
8908
|
+
}
|
|
8909
|
+
break;
|
|
8910
|
+
}
|
|
8911
|
+
}
|
|
8912
|
+
}
|
|
8913
|
+
}
|
|
8914
|
+
function* normalizeDelta(event, cursor, onSearchCall) {
|
|
8915
|
+
const delta = event.delta;
|
|
8916
|
+
switch (delta.type) {
|
|
8917
|
+
case "text": {
|
|
8918
|
+
const textDelta = delta;
|
|
8919
|
+
yield { type: "text", delta: textDelta.text, cursor, rawEvent: event };
|
|
8920
|
+
break;
|
|
8921
|
+
}
|
|
8922
|
+
// Citations arrive as dedicated annotation deltas in the current schema.
|
|
8923
|
+
case "text_annotation_delta": {
|
|
8924
|
+
const annotationDelta = delta;
|
|
8925
|
+
for (const annotation of annotationDelta.annotations ?? []) {
|
|
8926
|
+
const citation = annotationToCitation(annotation);
|
|
8927
|
+
if (citation) {
|
|
8928
|
+
yield { type: "citation", citation, cursor, rawEvent: event };
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
break;
|
|
8932
|
+
}
|
|
8933
|
+
// The SDK emits "thought_summary"; some preview docs show "thought" —
|
|
8934
|
+
// handle both defensively.
|
|
8935
|
+
case "thought_summary":
|
|
8936
|
+
case "thought": {
|
|
8937
|
+
const thought = delta;
|
|
8938
|
+
const text3 = thought.content?.type === "text" ? thought.content.text ?? "" : "";
|
|
8939
|
+
if (text3.length > 0) {
|
|
8940
|
+
yield { type: "thinking", delta: text3, cursor, rawEvent: event };
|
|
8941
|
+
}
|
|
8942
|
+
break;
|
|
8943
|
+
}
|
|
8944
|
+
case "thought_signature":
|
|
8945
|
+
break;
|
|
8946
|
+
case "google_search_call": {
|
|
8947
|
+
const call = delta;
|
|
8948
|
+
onSearchCall(call.arguments?.queries?.length ?? 1);
|
|
8949
|
+
yield {
|
|
8950
|
+
type: "search",
|
|
8951
|
+
action: "search",
|
|
8952
|
+
status: "started",
|
|
8953
|
+
query: call.arguments?.queries?.join("; "),
|
|
8954
|
+
cursor,
|
|
8955
|
+
rawEvent: event
|
|
8956
|
+
};
|
|
8957
|
+
break;
|
|
8958
|
+
}
|
|
8959
|
+
case "google_search_result": {
|
|
8960
|
+
const result = delta;
|
|
8961
|
+
yield {
|
|
8962
|
+
type: "search",
|
|
8963
|
+
action: "search",
|
|
8964
|
+
status: "completed",
|
|
8965
|
+
url: result.result?.[0]?.url,
|
|
8966
|
+
cursor,
|
|
8967
|
+
rawEvent: event
|
|
8968
|
+
};
|
|
8969
|
+
break;
|
|
8970
|
+
}
|
|
8971
|
+
case "url_context_call": {
|
|
8972
|
+
const call = delta;
|
|
8973
|
+
yield {
|
|
8974
|
+
type: "search",
|
|
8975
|
+
action: "open_page",
|
|
8976
|
+
status: "started",
|
|
8977
|
+
url: call.arguments?.urls?.[0],
|
|
8978
|
+
cursor,
|
|
8979
|
+
rawEvent: event
|
|
8980
|
+
};
|
|
8981
|
+
break;
|
|
8982
|
+
}
|
|
8983
|
+
case "url_context_result":
|
|
8984
|
+
yield { type: "search", action: "open_page", status: "completed", cursor, rawEvent: event };
|
|
8985
|
+
break;
|
|
8986
|
+
case "code_execution_call":
|
|
8987
|
+
yield { type: "tool", tool: "code_interpreter", status: "started", cursor, rawEvent: event };
|
|
8988
|
+
break;
|
|
8989
|
+
case "code_execution_result":
|
|
8990
|
+
yield {
|
|
8991
|
+
type: "tool",
|
|
8992
|
+
tool: "code_interpreter",
|
|
8993
|
+
status: "completed",
|
|
8994
|
+
cursor,
|
|
8995
|
+
rawEvent: event
|
|
8996
|
+
};
|
|
8997
|
+
break;
|
|
8998
|
+
case "file_search_call":
|
|
8999
|
+
yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
|
|
9000
|
+
break;
|
|
9001
|
+
case "file_search_result":
|
|
9002
|
+
yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
|
|
9003
|
+
break;
|
|
9004
|
+
case "mcp_server_tool_call":
|
|
9005
|
+
yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
|
|
9006
|
+
break;
|
|
9007
|
+
case "mcp_server_tool_result":
|
|
9008
|
+
yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
|
|
9009
|
+
break;
|
|
9010
|
+
default:
|
|
9011
|
+
break;
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
function abortError() {
|
|
9015
|
+
const error = new Error("The operation was aborted");
|
|
9016
|
+
error.name = "AbortError";
|
|
9017
|
+
return error;
|
|
9018
|
+
}
|
|
9019
|
+
function sleep(ms, signal) {
|
|
9020
|
+
return new Promise((resolve2, reject) => {
|
|
9021
|
+
if (signal?.aborted) {
|
|
9022
|
+
reject(abortError());
|
|
9023
|
+
return;
|
|
9024
|
+
}
|
|
9025
|
+
const timer = setTimeout(() => {
|
|
9026
|
+
signal?.removeEventListener("abort", onAbort);
|
|
9027
|
+
resolve2();
|
|
9028
|
+
}, ms);
|
|
9029
|
+
timer.unref?.();
|
|
9030
|
+
const onAbort = () => {
|
|
9031
|
+
clearTimeout(timer);
|
|
9032
|
+
reject(abortError());
|
|
9033
|
+
};
|
|
9034
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
9035
|
+
});
|
|
9036
|
+
}
|
|
9037
|
+
async function withRetries(fn, maxRetries, signal) {
|
|
9038
|
+
let attempt = 0;
|
|
9039
|
+
while (true) {
|
|
9040
|
+
try {
|
|
9041
|
+
return await fn();
|
|
9042
|
+
} catch (error) {
|
|
9043
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
9044
|
+
if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
|
|
9045
|
+
throw err;
|
|
9046
|
+
}
|
|
9047
|
+
attempt += 1;
|
|
9048
|
+
await sleep(extractRetryAfterMs(err) ?? RESEARCH_POLL_INTERVAL_MS, signal);
|
|
9049
|
+
}
|
|
9050
|
+
}
|
|
9051
|
+
}
|
|
9052
|
+
async function* withTerminalReconciliation(events, client, jobIdHint, signal) {
|
|
9053
|
+
let jobId = jobIdHint;
|
|
9054
|
+
let sawDone = false;
|
|
9055
|
+
for await (const event of events) {
|
|
9056
|
+
if (event.type === "created" && event.jobId !== null) jobId = event.jobId;
|
|
9057
|
+
if (event.type === "done") sawDone = true;
|
|
9058
|
+
yield event;
|
|
9059
|
+
}
|
|
9060
|
+
if (sawDone || jobId === void 0) return;
|
|
9061
|
+
const fetchInteraction = finalInteractionFetcher(client, signal);
|
|
9062
|
+
while (true) {
|
|
9063
|
+
const interaction = await fetchInteraction(jobId);
|
|
9064
|
+
const status = mapInteractionStatus(interaction.status);
|
|
9065
|
+
yield { type: "status", status };
|
|
9066
|
+
if (isTerminalStatus(status)) {
|
|
9067
|
+
yield { type: "usage", usage: usageFromInteraction(interaction) };
|
|
9068
|
+
yield doneEventFromInteraction(interaction);
|
|
9069
|
+
return;
|
|
9070
|
+
}
|
|
9071
|
+
await sleep(RESEARCH_POLL_INTERVAL_MS, signal);
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
function startGeminiResearch(client, options, agentId, _spec) {
|
|
9075
|
+
const request = buildGeminiResearchRequest(options, agentId);
|
|
9076
|
+
return (async function* () {
|
|
9077
|
+
const interaction = await withRetries(
|
|
9078
|
+
() => client.interactions.create(request, requestOptions(options.signal)),
|
|
9079
|
+
GEMINI_RESEARCH_CREATE_MAX_RETRIES,
|
|
9080
|
+
options.signal
|
|
9081
|
+
);
|
|
9082
|
+
if (interaction.id === void 0) {
|
|
9083
|
+
throw new Error(
|
|
9084
|
+
"Gemini interactions.create returned no interaction id \u2014 cannot stream events."
|
|
9085
|
+
);
|
|
9086
|
+
}
|
|
9087
|
+
yield { type: "created", jobId: interaction.id, rawEvent: interaction };
|
|
9088
|
+
yield { type: "status", status: mapInteractionStatus(interaction.status) };
|
|
9089
|
+
const stream2 = await client.interactions.get(
|
|
9090
|
+
interaction.id,
|
|
9091
|
+
{ stream: true },
|
|
9092
|
+
requestOptions(options.signal)
|
|
9093
|
+
);
|
|
9094
|
+
for await (const event of withTerminalReconciliation(
|
|
9095
|
+
normalizeInteractionsStream(stream2, finalInteractionFetcher(client, options.signal)),
|
|
9096
|
+
client,
|
|
9097
|
+
interaction.id,
|
|
9098
|
+
options.signal
|
|
9099
|
+
)) {
|
|
9100
|
+
if (event.type === "created") continue;
|
|
9101
|
+
yield event;
|
|
9102
|
+
}
|
|
9103
|
+
})();
|
|
9104
|
+
}
|
|
9105
|
+
function isTerminalStatus(status) {
|
|
9106
|
+
return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete" || status === "budget_exceeded";
|
|
9107
|
+
}
|
|
9108
|
+
function resumeGeminiResearch(client, ref, signal) {
|
|
9109
|
+
return (async function* () {
|
|
9110
|
+
const current = await client.interactions.get(
|
|
9111
|
+
ref.jobId,
|
|
9112
|
+
void 0,
|
|
9113
|
+
requestOptions(signal)
|
|
9114
|
+
);
|
|
9115
|
+
const status = mapInteractionStatus(current.status);
|
|
9116
|
+
if (isTerminalStatus(status)) {
|
|
9117
|
+
yield { type: "status", status };
|
|
9118
|
+
const { report, citations } = extractReportFromInteraction(current);
|
|
9119
|
+
if (report.length > 0) {
|
|
9120
|
+
yield { type: "text", delta: report };
|
|
9121
|
+
}
|
|
9122
|
+
for (const citation of citations) {
|
|
9123
|
+
yield { type: "citation", citation };
|
|
9124
|
+
}
|
|
9125
|
+
yield { type: "usage", usage: usageFromInteraction(current) };
|
|
9126
|
+
yield doneEventFromInteraction(current);
|
|
9127
|
+
return;
|
|
9128
|
+
}
|
|
9129
|
+
const stream2 = await client.interactions.get(
|
|
9130
|
+
ref.jobId,
|
|
9131
|
+
{ stream: true, ...ref.cursor !== void 0 ? { last_event_id: ref.cursor } : {} },
|
|
9132
|
+
requestOptions(signal)
|
|
9133
|
+
);
|
|
9134
|
+
yield* withTerminalReconciliation(
|
|
9135
|
+
normalizeInteractionsStream(stream2, finalInteractionFetcher(client, signal)),
|
|
9136
|
+
client,
|
|
9137
|
+
ref.jobId,
|
|
9138
|
+
signal
|
|
9139
|
+
);
|
|
9140
|
+
})();
|
|
9141
|
+
}
|
|
9142
|
+
async function getGeminiResearchStatus(client, ref) {
|
|
9143
|
+
const interaction = await client.interactions.get(
|
|
9144
|
+
ref.jobId
|
|
9145
|
+
);
|
|
9146
|
+
const status = mapInteractionStatus(interaction.status);
|
|
9147
|
+
if (!isTerminalStatus(status)) {
|
|
9148
|
+
return { status };
|
|
9149
|
+
}
|
|
9150
|
+
const { report, citations } = extractReportFromInteraction(interaction);
|
|
9151
|
+
return {
|
|
9152
|
+
status,
|
|
9153
|
+
result: {
|
|
9154
|
+
jobId: interaction.id,
|
|
9155
|
+
provider: "gemini",
|
|
9156
|
+
model: ref.model,
|
|
9157
|
+
status,
|
|
9158
|
+
report,
|
|
9159
|
+
citations,
|
|
9160
|
+
usage: usageFromInteraction(interaction),
|
|
9161
|
+
raw: interaction
|
|
9162
|
+
}
|
|
9163
|
+
};
|
|
9164
|
+
}
|
|
9165
|
+
async function cancelGeminiResearch(client, ref) {
|
|
9166
|
+
await client.interactions.cancel(ref.jobId);
|
|
9167
|
+
}
|
|
9168
|
+
var GEMINI_RESEARCH_CREATE_MAX_RETRIES, GEMINI_STATUS_VALUES, requestOptions, finalInteractionFetcher;
|
|
9169
|
+
var init_gemini_research = __esm({
|
|
9170
|
+
"src/providers/gemini-research.ts"() {
|
|
9171
|
+
"use strict";
|
|
9172
|
+
init_errors();
|
|
9173
|
+
init_retry();
|
|
9174
|
+
init_constants3();
|
|
9175
|
+
init_errors2();
|
|
9176
|
+
GEMINI_RESEARCH_CREATE_MAX_RETRIES = 3;
|
|
9177
|
+
GEMINI_STATUS_VALUES = /* @__PURE__ */ new Set([
|
|
9178
|
+
"queued",
|
|
9179
|
+
"in_progress",
|
|
9180
|
+
"requires_action",
|
|
9181
|
+
"completed",
|
|
9182
|
+
"failed",
|
|
9183
|
+
"cancelled",
|
|
9184
|
+
"incomplete",
|
|
9185
|
+
"budget_exceeded"
|
|
9186
|
+
]);
|
|
9187
|
+
requestOptions = (signal) => ({
|
|
9188
|
+
timeout: GEMINI_RESEARCH_MAX_DURATION_MS,
|
|
9189
|
+
signal,
|
|
9190
|
+
// The SDK's own retries would fight llmist's retry policy.
|
|
9191
|
+
maxRetries: 0
|
|
9192
|
+
});
|
|
9193
|
+
finalInteractionFetcher = (client, signal) => (id) => client.interactions.get(
|
|
9194
|
+
id,
|
|
9195
|
+
void 0,
|
|
9196
|
+
requestOptions(signal)
|
|
9197
|
+
);
|
|
9198
|
+
}
|
|
9199
|
+
});
|
|
9200
|
+
|
|
9201
|
+
// src/providers/gemini-research-models.ts
|
|
9202
|
+
function geminiResearchAgent(params) {
|
|
9203
|
+
return {
|
|
9204
|
+
provider: "gemini",
|
|
9205
|
+
modelId: params.agentId,
|
|
9206
|
+
kind: "agent",
|
|
9207
|
+
displayName: params.displayName,
|
|
9208
|
+
// Gemini 3.1 Pro base-tier token rates; >200K-context tier is higher.
|
|
9209
|
+
pricing: { input: 2, output: 12, perThousandSearches: 14 },
|
|
9210
|
+
capabilities: {
|
|
9211
|
+
streaming: true,
|
|
9212
|
+
// The Interactions API requires background execution for deep research.
|
|
9213
|
+
background: true,
|
|
9214
|
+
resumable: true,
|
|
9215
|
+
followUps: true,
|
|
9216
|
+
// Research tools are agent-managed — the tools option is not accepted.
|
|
9217
|
+
tools: []
|
|
9218
|
+
},
|
|
9219
|
+
// Interactions enforces a 60-minute research cap server-side.
|
|
9220
|
+
maxDurationMs: GEMINI_RESEARCH_MAX_DURATION_MS,
|
|
9221
|
+
metadata: params.notes ? { notes: params.notes } : void 0
|
|
9222
|
+
};
|
|
9223
|
+
}
|
|
9224
|
+
function isGeminiResearchModel(agentId) {
|
|
9225
|
+
return byId.has(agentId);
|
|
9226
|
+
}
|
|
9227
|
+
var geminiResearchModels, byId;
|
|
9228
|
+
var init_gemini_research_models = __esm({
|
|
9229
|
+
"src/providers/gemini-research-models.ts"() {
|
|
9230
|
+
"use strict";
|
|
9231
|
+
init_constants3();
|
|
9232
|
+
geminiResearchModels = [
|
|
9233
|
+
geminiResearchAgent({
|
|
9234
|
+
agentId: "deep-research-preview-04-2026",
|
|
9235
|
+
displayName: "Gemini Deep Research (preview 04-2026)",
|
|
9236
|
+
notes: "Speed-optimized deep research agent on a Gemini 3.1 Pro core."
|
|
9237
|
+
}),
|
|
9238
|
+
geminiResearchAgent({
|
|
9239
|
+
agentId: "deep-research-max-preview-04-2026",
|
|
9240
|
+
displayName: "Gemini Deep Research Max (preview 04-2026)",
|
|
9241
|
+
notes: "Maximum-comprehensiveness variant; roughly 2x the searches and tokens per run."
|
|
9242
|
+
}),
|
|
9243
|
+
geminiResearchAgent({
|
|
9244
|
+
agentId: "deep-research-pro-preview-12-2025",
|
|
9245
|
+
displayName: "Gemini Deep Research Pro (preview 12-2025)",
|
|
9246
|
+
notes: "Original deep research agent (Gemini 3 Pro core); prefer the 04-2026 agents."
|
|
9247
|
+
})
|
|
9248
|
+
];
|
|
9249
|
+
byId = new Map(geminiResearchModels.map((spec) => [spec.modelId, spec]));
|
|
9250
|
+
}
|
|
9251
|
+
});
|
|
9252
|
+
|
|
8604
9253
|
// src/providers/gemini-speech-models.ts
|
|
8605
9254
|
function getGeminiSpeechModelSpec(modelId) {
|
|
8606
9255
|
return geminiSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -8798,6 +9447,8 @@ var init_gemini = __esm({
|
|
|
8798
9447
|
init_gemini_cache_manager();
|
|
8799
9448
|
init_gemini_image_models();
|
|
8800
9449
|
init_gemini_models();
|
|
9450
|
+
init_gemini_research();
|
|
9451
|
+
init_gemini_research_models();
|
|
8801
9452
|
init_gemini_speech_models();
|
|
8802
9453
|
init_utils();
|
|
8803
9454
|
GEMINI3_PRO_THINKING_LEVEL = {
|
|
@@ -9017,6 +9668,27 @@ var init_gemini = __esm({
|
|
|
9017
9668
|
format: spec?.defaultFormat ?? "wav"
|
|
9018
9669
|
};
|
|
9019
9670
|
}
|
|
9671
|
+
// =========================================================================
|
|
9672
|
+
// Deep Research (Interactions API)
|
|
9673
|
+
// =========================================================================
|
|
9674
|
+
getResearchModelSpecs() {
|
|
9675
|
+
return geminiResearchModels;
|
|
9676
|
+
}
|
|
9677
|
+
supportsResearch(agentId) {
|
|
9678
|
+
return isGeminiResearchModel(agentId);
|
|
9679
|
+
}
|
|
9680
|
+
startResearch(options, descriptor, spec) {
|
|
9681
|
+
return startGeminiResearch(this.client, options, descriptor.name, spec);
|
|
9682
|
+
}
|
|
9683
|
+
resumeResearch(ref, signal) {
|
|
9684
|
+
return resumeGeminiResearch(this.client, ref, signal);
|
|
9685
|
+
}
|
|
9686
|
+
getResearchStatus(ref) {
|
|
9687
|
+
return getGeminiResearchStatus(this.client, ref);
|
|
9688
|
+
}
|
|
9689
|
+
cancelResearch(ref) {
|
|
9690
|
+
return cancelGeminiResearch(this.client, ref);
|
|
9691
|
+
}
|
|
9020
9692
|
buildApiRequest(options, descriptor, _spec, messages) {
|
|
9021
9693
|
const contents = this.convertMessagesToContents(messages);
|
|
9022
9694
|
return this.buildApiRequestFromContents(options, descriptor, _spec, contents, null, 0);
|
|
@@ -10243,17 +10915,17 @@ var init_openai_compatible_provider = __esm({
|
|
|
10243
10915
|
async executeStreamRequest(payload, signal) {
|
|
10244
10916
|
const client = this.client;
|
|
10245
10917
|
const headers = this.getCustomHeaders();
|
|
10246
|
-
const
|
|
10918
|
+
const requestOptions3 = {};
|
|
10247
10919
|
if (signal) {
|
|
10248
|
-
|
|
10920
|
+
requestOptions3.signal = signal;
|
|
10249
10921
|
}
|
|
10250
10922
|
if (Object.keys(headers).length > 0) {
|
|
10251
|
-
|
|
10923
|
+
requestOptions3.headers = headers;
|
|
10252
10924
|
}
|
|
10253
10925
|
try {
|
|
10254
10926
|
const stream2 = await client.chat.completions.create(
|
|
10255
10927
|
payload,
|
|
10256
|
-
Object.keys(
|
|
10928
|
+
Object.keys(requestOptions3).length > 0 ? requestOptions3 : void 0
|
|
10257
10929
|
);
|
|
10258
10930
|
return stream2;
|
|
10259
10931
|
} catch (error) {
|
|
@@ -11130,53 +11802,596 @@ var init_openai_models = __esm({
|
|
|
11130
11802
|
}
|
|
11131
11803
|
});
|
|
11132
11804
|
|
|
11133
|
-
// src/providers/openai-
|
|
11134
|
-
function
|
|
11135
|
-
|
|
11805
|
+
// src/providers/openai-research.ts
|
|
11806
|
+
function buildTools(options) {
|
|
11807
|
+
const tools = [];
|
|
11808
|
+
for (const tool of options.tools ?? []) {
|
|
11809
|
+
switch (tool.type) {
|
|
11810
|
+
case "web_search":
|
|
11811
|
+
tools.push({ type: "web_search_preview" });
|
|
11812
|
+
break;
|
|
11813
|
+
case "file_search":
|
|
11814
|
+
if (tool.vectorStoreIds.length > OPENAI_FILE_SEARCH_MAX_VECTOR_STORES) {
|
|
11815
|
+
throw new ResearchValidationError(
|
|
11816
|
+
`OpenAI file_search accepts at most ${OPENAI_FILE_SEARCH_MAX_VECTOR_STORES} vector stores (got ${tool.vectorStoreIds.length}).`
|
|
11817
|
+
);
|
|
11818
|
+
}
|
|
11819
|
+
tools.push({ type: "file_search", vector_store_ids: tool.vectorStoreIds });
|
|
11820
|
+
break;
|
|
11821
|
+
case "mcp":
|
|
11822
|
+
tools.push({
|
|
11823
|
+
type: "mcp",
|
|
11824
|
+
server_label: tool.serverLabel,
|
|
11825
|
+
server_url: tool.serverUrl,
|
|
11826
|
+
require_approval: tool.requireApproval ?? "never"
|
|
11827
|
+
});
|
|
11828
|
+
break;
|
|
11829
|
+
case "code_interpreter":
|
|
11830
|
+
tools.push({ type: "code_interpreter", container: { type: "auto" } });
|
|
11831
|
+
break;
|
|
11832
|
+
}
|
|
11833
|
+
}
|
|
11834
|
+
return tools;
|
|
11136
11835
|
}
|
|
11137
|
-
function
|
|
11138
|
-
|
|
11836
|
+
function buildResponsesResearchRequest(options, spec, modelId) {
|
|
11837
|
+
const tools = buildTools(options);
|
|
11838
|
+
const hasDataSource = (options.tools ?? []).some(
|
|
11839
|
+
(tool) => ["web_search", "file_search", "mcp"].includes(tool.type)
|
|
11840
|
+
);
|
|
11841
|
+
if (!hasDataSource) {
|
|
11842
|
+
throw new ResearchValidationError(
|
|
11843
|
+
"OpenAI deep research requires at least one data-source tool (web_search, file_search, or mcp)."
|
|
11844
|
+
);
|
|
11845
|
+
}
|
|
11846
|
+
const background = options.background ?? spec?.capabilities.background ?? true;
|
|
11847
|
+
const input = options.systemPrompt ? `${options.systemPrompt}
|
|
11848
|
+
|
|
11849
|
+
${options.query}` : options.query;
|
|
11850
|
+
const request = {
|
|
11851
|
+
model: modelId,
|
|
11852
|
+
input,
|
|
11853
|
+
background,
|
|
11854
|
+
// Background mode requires stored responses; polling/resume need them too.
|
|
11855
|
+
...background ? { store: true } : {},
|
|
11856
|
+
reasoning: {
|
|
11857
|
+
summary: "auto",
|
|
11858
|
+
...options.reasoning?.effort && options.reasoning.effort !== "none" ? { effort: mapEffort(options.reasoning.effort) } : {}
|
|
11859
|
+
},
|
|
11860
|
+
tools,
|
|
11861
|
+
...options.maxToolCalls !== void 0 ? { max_tool_calls: options.maxToolCalls } : {},
|
|
11862
|
+
...options.extra ?? {}
|
|
11863
|
+
};
|
|
11864
|
+
return request;
|
|
11139
11865
|
}
|
|
11140
|
-
function
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
|
|
11866
|
+
function mapEffort(effort) {
|
|
11867
|
+
switch (effort) {
|
|
11868
|
+
case "maximum":
|
|
11869
|
+
return "xhigh";
|
|
11870
|
+
case "low":
|
|
11871
|
+
case "medium":
|
|
11872
|
+
case "high":
|
|
11873
|
+
return effort;
|
|
11874
|
+
default:
|
|
11875
|
+
return "medium";
|
|
11145
11876
|
}
|
|
11146
|
-
|
|
11147
|
-
|
|
11877
|
+
}
|
|
11878
|
+
function extractReportFromResponse(response) {
|
|
11879
|
+
const citations = [];
|
|
11880
|
+
const parts = [];
|
|
11881
|
+
for (const item of response.output ?? []) {
|
|
11882
|
+
if (item.type !== "message") continue;
|
|
11883
|
+
for (const content of item.content ?? []) {
|
|
11884
|
+
if (content.type !== "output_text") continue;
|
|
11885
|
+
parts.push(content.text);
|
|
11886
|
+
for (const annotation of content.annotations ?? []) {
|
|
11887
|
+
const citation = annotationToCitation2(annotation);
|
|
11888
|
+
if (citation) citations.push(citation);
|
|
11889
|
+
}
|
|
11890
|
+
}
|
|
11148
11891
|
}
|
|
11149
|
-
|
|
11150
|
-
|
|
11151
|
-
|
|
11892
|
+
return { report: parts.join(""), citations };
|
|
11893
|
+
}
|
|
11894
|
+
function annotationToCitation2(annotation) {
|
|
11895
|
+
if (typeof annotation !== "object" || annotation === null) return void 0;
|
|
11896
|
+
const a = annotation;
|
|
11897
|
+
if (a.type !== "url_citation" || typeof a.url !== "string") return void 0;
|
|
11898
|
+
return {
|
|
11899
|
+
url: a.url,
|
|
11900
|
+
title: typeof a.title === "string" ? a.title : void 0,
|
|
11901
|
+
startIndex: typeof a.start_index === "number" ? a.start_index : void 0,
|
|
11902
|
+
endIndex: typeof a.end_index === "number" ? a.end_index : void 0
|
|
11903
|
+
};
|
|
11904
|
+
}
|
|
11905
|
+
function countSearches(response) {
|
|
11906
|
+
let searches = 0;
|
|
11907
|
+
for (const item of response.output ?? []) {
|
|
11908
|
+
if (item.type === "web_search_call") searches += 1;
|
|
11152
11909
|
}
|
|
11153
|
-
return
|
|
11910
|
+
return searches;
|
|
11154
11911
|
}
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11158
|
-
|
|
11159
|
-
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11912
|
+
function usageFromResponse(response) {
|
|
11913
|
+
const usage = response.usage;
|
|
11914
|
+
return {
|
|
11915
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
11916
|
+
outputTokens: usage?.output_tokens ?? 0,
|
|
11917
|
+
totalTokens: usage?.total_tokens ?? 0,
|
|
11918
|
+
cachedInputTokens: usage?.input_tokens_details?.cached_tokens,
|
|
11919
|
+
reasoningTokens: usage?.output_tokens_details?.reasoning_tokens,
|
|
11920
|
+
searches: countSearches(response)
|
|
11921
|
+
};
|
|
11922
|
+
}
|
|
11923
|
+
function mapResponseStatus(status) {
|
|
11924
|
+
return status && STATUS_MAP[status] || "in_progress";
|
|
11925
|
+
}
|
|
11926
|
+
function doneEventFromResponse(response) {
|
|
11927
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
11928
|
+
return {
|
|
11929
|
+
type: "done",
|
|
11930
|
+
result: {
|
|
11931
|
+
status: mapResponseStatus(response.status),
|
|
11932
|
+
report,
|
|
11933
|
+
citations,
|
|
11934
|
+
usage: usageFromResponse(response),
|
|
11935
|
+
raw: response
|
|
11936
|
+
}
|
|
11937
|
+
};
|
|
11938
|
+
}
|
|
11939
|
+
async function* normalizeResponsesStream(stream2) {
|
|
11940
|
+
const searchStarted = /* @__PURE__ */ new Set();
|
|
11941
|
+
for await (const event of stream2) {
|
|
11942
|
+
const cursor = String(event.sequence_number);
|
|
11943
|
+
switch (event.type) {
|
|
11944
|
+
case "response.created":
|
|
11945
|
+
yield { type: "created", jobId: event.response.id, cursor, rawEvent: event };
|
|
11946
|
+
yield { type: "status", status: mapResponseStatus(event.response.status), cursor };
|
|
11947
|
+
break;
|
|
11948
|
+
case "response.queued":
|
|
11949
|
+
yield { type: "status", status: "queued", cursor, rawEvent: event };
|
|
11950
|
+
break;
|
|
11951
|
+
case "response.in_progress":
|
|
11952
|
+
yield { type: "status", status: "in_progress", cursor, rawEvent: event };
|
|
11953
|
+
break;
|
|
11954
|
+
case "response.output_item.added": {
|
|
11955
|
+
const item = event.item;
|
|
11956
|
+
if (item.type === "reasoning") {
|
|
11957
|
+
yield { type: "phase", phase: "reasoning", cursor, rawEvent: event };
|
|
11958
|
+
} else if (item.type === "web_search_call") {
|
|
11959
|
+
const action = "action" in item ? item.action : void 0;
|
|
11960
|
+
searchStarted.add(item.id);
|
|
11961
|
+
yield {
|
|
11962
|
+
type: "search",
|
|
11963
|
+
action: action?.type ?? "search",
|
|
11964
|
+
status: "started",
|
|
11965
|
+
query: action && "query" in action ? action.query ?? void 0 : void 0,
|
|
11966
|
+
url: action && "url" in action ? action.url ?? void 0 : void 0,
|
|
11967
|
+
cursor,
|
|
11968
|
+
rawEvent: event
|
|
11969
|
+
};
|
|
11970
|
+
} else if (item.type === "code_interpreter_call") {
|
|
11971
|
+
yield {
|
|
11972
|
+
type: "tool",
|
|
11973
|
+
tool: "code_interpreter",
|
|
11974
|
+
status: "started",
|
|
11975
|
+
cursor,
|
|
11976
|
+
rawEvent: event
|
|
11977
|
+
};
|
|
11978
|
+
} else if (item.type === "file_search_call") {
|
|
11979
|
+
yield { type: "tool", tool: "file_search", status: "started", cursor, rawEvent: event };
|
|
11980
|
+
} else if (item.type === "mcp_call") {
|
|
11981
|
+
yield { type: "tool", tool: "mcp", status: "started", cursor, rawEvent: event };
|
|
11982
|
+
} else if (item.type === "message") {
|
|
11983
|
+
yield { type: "phase", phase: "writing", cursor, rawEvent: event };
|
|
11984
|
+
}
|
|
11985
|
+
break;
|
|
11986
|
+
}
|
|
11987
|
+
case "response.web_search_call.in_progress":
|
|
11988
|
+
case "response.web_search_call.searching":
|
|
11989
|
+
if (!searchStarted.has(event.item_id)) {
|
|
11990
|
+
searchStarted.add(event.item_id);
|
|
11991
|
+
yield { type: "search", action: "search", status: "started", cursor, rawEvent: event };
|
|
11992
|
+
}
|
|
11993
|
+
break;
|
|
11994
|
+
case "response.web_search_call.completed":
|
|
11995
|
+
yield { type: "search", action: "search", status: "completed", cursor, rawEvent: event };
|
|
11996
|
+
break;
|
|
11997
|
+
case "response.code_interpreter_call.completed":
|
|
11998
|
+
yield {
|
|
11999
|
+
type: "tool",
|
|
12000
|
+
tool: "code_interpreter",
|
|
12001
|
+
status: "completed",
|
|
12002
|
+
cursor,
|
|
12003
|
+
rawEvent: event
|
|
12004
|
+
};
|
|
12005
|
+
break;
|
|
12006
|
+
case "response.file_search_call.completed":
|
|
12007
|
+
yield { type: "tool", tool: "file_search", status: "completed", cursor, rawEvent: event };
|
|
12008
|
+
break;
|
|
12009
|
+
case "response.mcp_call.completed":
|
|
12010
|
+
yield { type: "tool", tool: "mcp", status: "completed", cursor, rawEvent: event };
|
|
12011
|
+
break;
|
|
12012
|
+
case "response.reasoning_summary_text.delta":
|
|
12013
|
+
yield { type: "thinking", delta: event.delta, cursor, rawEvent: event };
|
|
12014
|
+
break;
|
|
12015
|
+
case "response.output_text.delta":
|
|
12016
|
+
yield { type: "text", delta: event.delta, cursor, rawEvent: event };
|
|
12017
|
+
break;
|
|
12018
|
+
case "response.output_text.annotation.added": {
|
|
12019
|
+
const citation = annotationToCitation2(event.annotation);
|
|
12020
|
+
if (citation) {
|
|
12021
|
+
yield { type: "citation", citation, cursor, rawEvent: event };
|
|
12022
|
+
}
|
|
12023
|
+
break;
|
|
12024
|
+
}
|
|
12025
|
+
case "response.completed":
|
|
12026
|
+
case "response.failed":
|
|
12027
|
+
case "response.incomplete": {
|
|
12028
|
+
const response = event.response;
|
|
12029
|
+
yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
|
|
12030
|
+
if (event.type === "response.failed") {
|
|
12031
|
+
const message = response.error?.message ?? "OpenAI research run failed";
|
|
12032
|
+
yield {
|
|
12033
|
+
type: "error",
|
|
12034
|
+
error: { message, code: response.error?.code ?? void 0, retryable: false },
|
|
12035
|
+
cursor
|
|
12036
|
+
};
|
|
12037
|
+
}
|
|
12038
|
+
yield { ...doneEventFromResponse(response), cursor };
|
|
12039
|
+
break;
|
|
12040
|
+
}
|
|
12041
|
+
case "error": {
|
|
12042
|
+
const err = new Error(event.message ?? "OpenAI stream error");
|
|
12043
|
+
yield {
|
|
12044
|
+
type: "error",
|
|
12045
|
+
error: {
|
|
12046
|
+
message: event.message ?? "OpenAI stream error",
|
|
12047
|
+
code: event.code ?? void 0,
|
|
12048
|
+
retryable: isRetryableError(err)
|
|
12049
|
+
},
|
|
12050
|
+
cursor,
|
|
12051
|
+
rawEvent: event
|
|
12052
|
+
};
|
|
12053
|
+
break;
|
|
12054
|
+
}
|
|
12055
|
+
default: {
|
|
12056
|
+
const type = event.type;
|
|
12057
|
+
if (type === "response.cancelled") {
|
|
12058
|
+
const response = event.response;
|
|
12059
|
+
yield { type: "usage", usage: usageFromResponse(response), cursor, rawEvent: event };
|
|
12060
|
+
yield { ...doneEventFromResponse(response), cursor };
|
|
12061
|
+
}
|
|
12062
|
+
break;
|
|
12063
|
+
}
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12067
|
+
function sleep2(ms, signal) {
|
|
12068
|
+
return new Promise((resolve2, reject) => {
|
|
12069
|
+
if (signal?.aborted) {
|
|
12070
|
+
reject(abortError2());
|
|
12071
|
+
return;
|
|
12072
|
+
}
|
|
12073
|
+
const timer = setTimeout(() => {
|
|
12074
|
+
signal?.removeEventListener("abort", onAbort);
|
|
12075
|
+
resolve2();
|
|
12076
|
+
}, ms);
|
|
12077
|
+
timer.unref?.();
|
|
12078
|
+
const onAbort = () => {
|
|
12079
|
+
clearTimeout(timer);
|
|
12080
|
+
reject(abortError2());
|
|
12081
|
+
};
|
|
12082
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
12083
|
+
});
|
|
12084
|
+
}
|
|
12085
|
+
function abortError2() {
|
|
12086
|
+
const error = new Error("The operation was aborted");
|
|
12087
|
+
error.name = "AbortError";
|
|
12088
|
+
return error;
|
|
12089
|
+
}
|
|
12090
|
+
async function withRetries2(fn, maxRetries, signal) {
|
|
12091
|
+
let attempt = 0;
|
|
12092
|
+
while (true) {
|
|
12093
|
+
try {
|
|
12094
|
+
return await fn();
|
|
12095
|
+
} catch (error) {
|
|
12096
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
12097
|
+
if (isAbortError(err) || !isRetryableError(err) || attempt >= maxRetries) {
|
|
12098
|
+
throw err;
|
|
12099
|
+
}
|
|
12100
|
+
attempt += 1;
|
|
12101
|
+
const retryAfter = extractRetryAfterMs(err);
|
|
12102
|
+
await sleep2(retryAfter ?? RESEARCH_POLL_INTERVAL_MS, signal);
|
|
12103
|
+
}
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
function startOpenAIResearch(client, options, modelId, spec) {
|
|
12107
|
+
const request = buildResponsesResearchRequest(options, spec, modelId);
|
|
12108
|
+
const streaming = spec?.capabilities.streaming ?? true;
|
|
12109
|
+
if (streaming) {
|
|
12110
|
+
return (async function* () {
|
|
12111
|
+
const stream2 = await withRetries2(
|
|
12112
|
+
() => client.responses.create(
|
|
12113
|
+
{ ...request, stream: true },
|
|
12114
|
+
requestOptions2(options.signal)
|
|
12115
|
+
),
|
|
12116
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES,
|
|
12117
|
+
options.signal
|
|
12118
|
+
);
|
|
12119
|
+
yield* normalizeResponsesStream(stream2);
|
|
12120
|
+
})();
|
|
12121
|
+
}
|
|
12122
|
+
return (async function* () {
|
|
12123
|
+
const created = await withRetries2(
|
|
12124
|
+
() => client.responses.create(
|
|
12125
|
+
{ ...request, background: true, store: true, stream: false },
|
|
12126
|
+
requestOptions2(options.signal)
|
|
12127
|
+
),
|
|
12128
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES,
|
|
12129
|
+
options.signal
|
|
12130
|
+
);
|
|
12131
|
+
yield { type: "created", jobId: created.id, rawEvent: created };
|
|
12132
|
+
yield* pollResponseToCompletion(client, created.id, created, options.signal);
|
|
12133
|
+
})();
|
|
12134
|
+
}
|
|
12135
|
+
function* emitTerminalResponse(response) {
|
|
12136
|
+
const status = mapResponseStatus(response.status);
|
|
12137
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
12138
|
+
if (report.length > 0) {
|
|
12139
|
+
yield { type: "text", delta: report };
|
|
12140
|
+
}
|
|
12141
|
+
for (const citation of citations) {
|
|
12142
|
+
yield { type: "citation", citation };
|
|
12143
|
+
}
|
|
12144
|
+
yield { type: "usage", usage: usageFromResponse(response) };
|
|
12145
|
+
if (status === "failed") {
|
|
12146
|
+
yield {
|
|
12147
|
+
type: "error",
|
|
12148
|
+
error: {
|
|
12149
|
+
message: response.error?.message ?? "OpenAI research run failed",
|
|
12150
|
+
code: response.error?.code ?? void 0,
|
|
12151
|
+
retryable: false
|
|
12152
|
+
}
|
|
12153
|
+
};
|
|
12154
|
+
}
|
|
12155
|
+
yield doneEventFromResponse(response);
|
|
12156
|
+
}
|
|
12157
|
+
async function* pollResponseToCompletion(client, responseId, initial, signal) {
|
|
12158
|
+
let interval = RESEARCH_POLL_INTERVAL_MS;
|
|
12159
|
+
let current = initial;
|
|
12160
|
+
while (true) {
|
|
12161
|
+
const status = mapResponseStatus(current?.status);
|
|
12162
|
+
yield { type: "status", status, rawEvent: current };
|
|
12163
|
+
if (current && isTerminalStatus2(status)) {
|
|
12164
|
+
yield* emitTerminalResponse(current);
|
|
12165
|
+
return;
|
|
12166
|
+
}
|
|
12167
|
+
await sleep2(interval, signal);
|
|
12168
|
+
interval = Math.min(interval * RESEARCH_POLL_BACKOFF_FACTOR, RESEARCH_POLL_MAX_INTERVAL_MS);
|
|
12169
|
+
current = await withRetries2(
|
|
12170
|
+
() => client.responses.retrieve(responseId, {}, requestOptions2(signal)),
|
|
12171
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES,
|
|
12172
|
+
signal
|
|
12173
|
+
);
|
|
12174
|
+
}
|
|
12175
|
+
}
|
|
12176
|
+
function isTerminalStatus2(status) {
|
|
12177
|
+
return status === "completed" || status === "failed" || status === "cancelled" || status === "incomplete";
|
|
12178
|
+
}
|
|
12179
|
+
function resumeOpenAIResearch(client, ref, spec, signal) {
|
|
12180
|
+
const streaming = spec?.capabilities.streaming ?? true;
|
|
12181
|
+
if (streaming) {
|
|
12182
|
+
return (async function* () {
|
|
12183
|
+
const current = await withRetries2(
|
|
12184
|
+
() => client.responses.retrieve(ref.jobId, {}, requestOptions2(signal)),
|
|
12185
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES,
|
|
12186
|
+
signal
|
|
12187
|
+
);
|
|
12188
|
+
if (isTerminalStatus2(mapResponseStatus(current.status))) {
|
|
12189
|
+
yield { type: "status", status: mapResponseStatus(current.status) };
|
|
12190
|
+
yield* emitTerminalResponse(current);
|
|
12191
|
+
return;
|
|
12192
|
+
}
|
|
12193
|
+
const stream2 = await client.responses.retrieve(
|
|
12194
|
+
ref.jobId,
|
|
12195
|
+
{
|
|
12196
|
+
stream: true,
|
|
12197
|
+
...ref.cursor !== void 0 ? { starting_after: Number(ref.cursor) } : {}
|
|
12198
|
+
},
|
|
12199
|
+
requestOptions2(signal)
|
|
12200
|
+
);
|
|
12201
|
+
yield* normalizeResponsesStream(stream2);
|
|
12202
|
+
})();
|
|
12203
|
+
}
|
|
12204
|
+
return pollResponseToCompletion(client, ref.jobId, void 0, signal);
|
|
12205
|
+
}
|
|
12206
|
+
async function getOpenAIResearchStatus(client, ref) {
|
|
12207
|
+
const response = await client.responses.retrieve(ref.jobId, {});
|
|
12208
|
+
const status = mapResponseStatus(response.status);
|
|
12209
|
+
if (!isTerminalStatus2(status)) {
|
|
12210
|
+
return { status };
|
|
12211
|
+
}
|
|
12212
|
+
const { report, citations } = extractReportFromResponse(response);
|
|
12213
|
+
return {
|
|
12214
|
+
status,
|
|
12215
|
+
result: {
|
|
12216
|
+
jobId: response.id,
|
|
12217
|
+
provider: "openai",
|
|
12218
|
+
model: ref.model,
|
|
12219
|
+
status,
|
|
12220
|
+
report,
|
|
12221
|
+
citations,
|
|
12222
|
+
usage: usageFromResponse(response),
|
|
12223
|
+
raw: response
|
|
12224
|
+
}
|
|
12225
|
+
};
|
|
12226
|
+
}
|
|
12227
|
+
async function cancelOpenAIResearch(client, ref) {
|
|
12228
|
+
await client.responses.cancel(ref.jobId);
|
|
12229
|
+
}
|
|
12230
|
+
var OPENAI_FILE_SEARCH_MAX_VECTOR_STORES, OPENAI_RESEARCH_CREATE_MAX_RETRIES, OPENAI_RESEARCH_POLL_MAX_RETRIES, STATUS_MAP, requestOptions2;
|
|
12231
|
+
var init_openai_research = __esm({
|
|
12232
|
+
"src/providers/openai-research.ts"() {
|
|
12233
|
+
"use strict";
|
|
12234
|
+
init_errors();
|
|
12235
|
+
init_retry();
|
|
12236
|
+
init_constants3();
|
|
12237
|
+
init_errors2();
|
|
12238
|
+
OPENAI_FILE_SEARCH_MAX_VECTOR_STORES = 2;
|
|
12239
|
+
OPENAI_RESEARCH_CREATE_MAX_RETRIES = 3;
|
|
12240
|
+
OPENAI_RESEARCH_POLL_MAX_RETRIES = 5;
|
|
12241
|
+
STATUS_MAP = {
|
|
12242
|
+
queued: "queued",
|
|
12243
|
+
in_progress: "in_progress",
|
|
12244
|
+
completed: "completed",
|
|
12245
|
+
failed: "failed",
|
|
12246
|
+
cancelled: "cancelled",
|
|
12247
|
+
incomplete: "incomplete"
|
|
12248
|
+
};
|
|
12249
|
+
requestOptions2 = (signal) => ({
|
|
12250
|
+
timeout: OPENAI_RESEARCH_HTTP_TIMEOUT_MS,
|
|
12251
|
+
signal
|
|
12252
|
+
});
|
|
12253
|
+
}
|
|
12254
|
+
});
|
|
12255
|
+
|
|
12256
|
+
// src/providers/openai-research-models.ts
|
|
12257
|
+
function o3DeepResearch(modelId) {
|
|
12258
|
+
return {
|
|
12259
|
+
provider: "openai",
|
|
12260
|
+
modelId,
|
|
12261
|
+
kind: "model",
|
|
12262
|
+
displayName: "o3 Deep Research",
|
|
12263
|
+
contextWindow: 2e5,
|
|
12264
|
+
maxOutputTokens: 1e5,
|
|
12265
|
+
pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
|
|
12266
|
+
capabilities: {
|
|
12267
|
+
streaming: true,
|
|
12268
|
+
background: true,
|
|
12269
|
+
resumable: true,
|
|
12270
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
12271
|
+
},
|
|
12272
|
+
requiredTools: [{ type: "web_search" }],
|
|
12273
|
+
metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
|
|
12274
|
+
};
|
|
12275
|
+
}
|
|
12276
|
+
function o4MiniDeepResearch(modelId) {
|
|
12277
|
+
return {
|
|
12278
|
+
provider: "openai",
|
|
12279
|
+
modelId,
|
|
12280
|
+
kind: "model",
|
|
12281
|
+
displayName: "o4-mini Deep Research",
|
|
12282
|
+
contextWindow: 2e5,
|
|
12283
|
+
maxOutputTokens: 1e5,
|
|
12284
|
+
pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
|
|
12285
|
+
capabilities: {
|
|
12286
|
+
streaming: true,
|
|
12287
|
+
background: true,
|
|
12288
|
+
resumable: true,
|
|
12289
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
12290
|
+
},
|
|
12291
|
+
requiredTools: [{ type: "web_search" }],
|
|
12292
|
+
metadata: { releaseDate: "2025-06-26", ...DEEP_RESEARCH_SHUTDOWN }
|
|
12293
|
+
};
|
|
12294
|
+
}
|
|
12295
|
+
function getOpenAIResearchModelSpec(modelId) {
|
|
12296
|
+
return byId2.get(modelId);
|
|
12297
|
+
}
|
|
12298
|
+
function isOpenAIResearchModel(modelId) {
|
|
12299
|
+
return byId2.has(modelId);
|
|
12300
|
+
}
|
|
12301
|
+
var DEEP_RESEARCH_TOOLS, DEEP_RESEARCH_SHUTDOWN, openaiResearchModels, byId2;
|
|
12302
|
+
var init_openai_research_models = __esm({
|
|
12303
|
+
"src/providers/openai-research-models.ts"() {
|
|
12304
|
+
"use strict";
|
|
12305
|
+
DEEP_RESEARCH_TOOLS = [
|
|
12306
|
+
"web_search",
|
|
12307
|
+
"file_search",
|
|
12308
|
+
"mcp",
|
|
12309
|
+
"code_interpreter"
|
|
12310
|
+
];
|
|
12311
|
+
DEEP_RESEARCH_SHUTDOWN = {
|
|
12312
|
+
deprecationDate: "2026-04-22",
|
|
12313
|
+
shutdownDate: "2026-07-23",
|
|
12314
|
+
replacement: "gpt-5.5-pro"
|
|
12315
|
+
};
|
|
12316
|
+
openaiResearchModels = [
|
|
12317
|
+
o3DeepResearch("o3-deep-research"),
|
|
12318
|
+
o3DeepResearch("o3-deep-research-2025-06-26"),
|
|
12319
|
+
o4MiniDeepResearch("o4-mini-deep-research"),
|
|
12320
|
+
o4MiniDeepResearch("o4-mini-deep-research-2025-06-26"),
|
|
12321
|
+
{
|
|
12322
|
+
provider: "openai",
|
|
12323
|
+
modelId: "gpt-5.5-pro",
|
|
12324
|
+
kind: "model",
|
|
12325
|
+
displayName: "GPT-5.5 Pro (research)",
|
|
12326
|
+
contextWindow: 105e4,
|
|
12327
|
+
maxOutputTokens: 128e3,
|
|
12328
|
+
pricing: { input: 30, output: 180, perThousandSearches: 10 },
|
|
12329
|
+
capabilities: {
|
|
12330
|
+
// No live streaming — runs execute via background create + poll.
|
|
12331
|
+
streaming: false,
|
|
12332
|
+
background: true,
|
|
12333
|
+
// Resumable in the attach sense: the poll loop re-attaches by job id.
|
|
12334
|
+
resumable: true,
|
|
12335
|
+
tools: DEEP_RESEARCH_TOOLS
|
|
12336
|
+
},
|
|
12337
|
+
requiredTools: [{ type: "web_search" }],
|
|
12338
|
+
metadata: {
|
|
12339
|
+
releaseDate: "2026-04-24",
|
|
12340
|
+
notes: "Designated replacement for the o3/o4-mini deep research models."
|
|
12341
|
+
}
|
|
12342
|
+
}
|
|
12343
|
+
];
|
|
12344
|
+
byId2 = new Map(openaiResearchModels.map((spec) => [spec.modelId, spec]));
|
|
12345
|
+
}
|
|
12346
|
+
});
|
|
12347
|
+
|
|
12348
|
+
// src/providers/openai-speech-models.ts
|
|
12349
|
+
function getOpenAISpeechModelSpec(modelId) {
|
|
12350
|
+
return openaiSpeechModels.find((m) => m.modelId === modelId);
|
|
12351
|
+
}
|
|
12352
|
+
function isOpenAISpeechModel(modelId) {
|
|
12353
|
+
return openaiSpeechModels.some((m) => m.modelId === modelId);
|
|
12354
|
+
}
|
|
12355
|
+
function calculateOpenAISpeechCost(modelId, characterCount, estimatedMinutes) {
|
|
12356
|
+
const spec = getOpenAISpeechModelSpec(modelId);
|
|
12357
|
+
if (!spec) return void 0;
|
|
12358
|
+
if (spec.pricing.perCharacter !== void 0) {
|
|
12359
|
+
return characterCount * spec.pricing.perCharacter;
|
|
12360
|
+
}
|
|
12361
|
+
if (spec.pricing.perMinute !== void 0 && estimatedMinutes !== void 0) {
|
|
12362
|
+
return estimatedMinutes * spec.pricing.perMinute;
|
|
12363
|
+
}
|
|
12364
|
+
if (spec.pricing.perMinute !== void 0) {
|
|
12365
|
+
const approxMinutes = characterCount / 750;
|
|
12366
|
+
return approxMinutes * spec.pricing.perMinute;
|
|
12367
|
+
}
|
|
12368
|
+
return void 0;
|
|
12369
|
+
}
|
|
12370
|
+
var OPENAI_TTS_VOICES, OPENAI_TTS_EXTENDED_VOICES, OPENAI_TTS_FORMATS, openaiSpeechModels;
|
|
12371
|
+
var init_openai_speech_models = __esm({
|
|
12372
|
+
"src/providers/openai-speech-models.ts"() {
|
|
12373
|
+
"use strict";
|
|
12374
|
+
OPENAI_TTS_VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"];
|
|
12375
|
+
OPENAI_TTS_EXTENDED_VOICES = [
|
|
12376
|
+
...OPENAI_TTS_VOICES,
|
|
12377
|
+
"ash",
|
|
12378
|
+
"ballad",
|
|
12379
|
+
"coral",
|
|
12380
|
+
"sage",
|
|
12381
|
+
"verse"
|
|
12382
|
+
];
|
|
12383
|
+
OPENAI_TTS_FORMATS = ["mp3", "opus", "aac", "flac", "wav", "pcm"];
|
|
12384
|
+
openaiSpeechModels = [
|
|
12385
|
+
// Standard TTS models (character-based pricing)
|
|
12386
|
+
{
|
|
12387
|
+
provider: "openai",
|
|
12388
|
+
modelId: "tts-1",
|
|
12389
|
+
displayName: "TTS-1",
|
|
12390
|
+
pricing: {
|
|
12391
|
+
// $15 per 1M characters = $0.000015 per character
|
|
12392
|
+
perCharacter: 15e-6
|
|
12393
|
+
},
|
|
12394
|
+
voices: [...OPENAI_TTS_VOICES],
|
|
11180
12395
|
formats: OPENAI_TTS_FORMATS,
|
|
11181
12396
|
maxInputLength: 4096,
|
|
11182
12397
|
defaultVoice: "alloy",
|
|
@@ -11285,6 +12500,8 @@ var init_openai = __esm({
|
|
|
11285
12500
|
init_constants2();
|
|
11286
12501
|
init_openai_image_models();
|
|
11287
12502
|
init_openai_models();
|
|
12503
|
+
init_openai_research();
|
|
12504
|
+
init_openai_research_models();
|
|
11288
12505
|
init_openai_speech_models();
|
|
11289
12506
|
init_utils();
|
|
11290
12507
|
ROLE_MAP2 = {
|
|
@@ -11388,6 +12605,32 @@ var init_openai = __esm({
|
|
|
11388
12605
|
format: format2
|
|
11389
12606
|
};
|
|
11390
12607
|
}
|
|
12608
|
+
// =========================================================================
|
|
12609
|
+
// Deep Research (Responses API)
|
|
12610
|
+
// =========================================================================
|
|
12611
|
+
getResearchModelSpecs() {
|
|
12612
|
+
return openaiResearchModels;
|
|
12613
|
+
}
|
|
12614
|
+
supportsResearch(modelId) {
|
|
12615
|
+
return isOpenAIResearchModel(modelId);
|
|
12616
|
+
}
|
|
12617
|
+
startResearch(options, descriptor, spec) {
|
|
12618
|
+
return startOpenAIResearch(this.client, options, descriptor.name, spec);
|
|
12619
|
+
}
|
|
12620
|
+
resumeResearch(ref, signal) {
|
|
12621
|
+
return resumeOpenAIResearch(
|
|
12622
|
+
this.client,
|
|
12623
|
+
ref,
|
|
12624
|
+
getOpenAIResearchModelSpec(ref.model),
|
|
12625
|
+
signal
|
|
12626
|
+
);
|
|
12627
|
+
}
|
|
12628
|
+
getResearchStatus(ref) {
|
|
12629
|
+
return getOpenAIResearchStatus(this.client, ref);
|
|
12630
|
+
}
|
|
12631
|
+
cancelResearch(ref) {
|
|
12632
|
+
return cancelOpenAIResearch(this.client, ref);
|
|
12633
|
+
}
|
|
11391
12634
|
buildApiRequest(options, descriptor, spec, messages) {
|
|
11392
12635
|
const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
|
|
11393
12636
|
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
@@ -12115,6 +13358,217 @@ var init_openrouter_models = __esm({
|
|
|
12115
13358
|
}
|
|
12116
13359
|
});
|
|
12117
13360
|
|
|
13361
|
+
// src/providers/openrouter-research.ts
|
|
13362
|
+
function buildOpenRouterResearchMessages(options) {
|
|
13363
|
+
if (options.background === true) {
|
|
13364
|
+
throw new ResearchValidationError(
|
|
13365
|
+
"OpenRouter has no background mode for research runs \u2014 the stream must stay open."
|
|
13366
|
+
);
|
|
13367
|
+
}
|
|
13368
|
+
if (options.tools !== void 0 && options.tools.length > 0) {
|
|
13369
|
+
throw new ResearchValidationError(
|
|
13370
|
+
"OpenRouter research models manage their tools server-side \u2014 omit the tools option."
|
|
13371
|
+
);
|
|
13372
|
+
}
|
|
13373
|
+
const messages = [];
|
|
13374
|
+
if (options.systemPrompt) {
|
|
13375
|
+
messages.push({ role: "system", content: options.systemPrompt });
|
|
13376
|
+
}
|
|
13377
|
+
messages.push({ role: "user", content: options.query });
|
|
13378
|
+
return { messages };
|
|
13379
|
+
}
|
|
13380
|
+
function citationKey(citation) {
|
|
13381
|
+
return `${citation.url}#${citation.startIndex ?? ""}`;
|
|
13382
|
+
}
|
|
13383
|
+
function mapFinishReason(reason) {
|
|
13384
|
+
switch (reason) {
|
|
13385
|
+
case "length":
|
|
13386
|
+
return "incomplete";
|
|
13387
|
+
case "error":
|
|
13388
|
+
return "failed";
|
|
13389
|
+
default:
|
|
13390
|
+
return "completed";
|
|
13391
|
+
}
|
|
13392
|
+
}
|
|
13393
|
+
async function* normalizeOpenRouterResearchStream(stream2) {
|
|
13394
|
+
let createdEmitted = false;
|
|
13395
|
+
const seenCitations = /* @__PURE__ */ new Set();
|
|
13396
|
+
const seenCitationUrls = /* @__PURE__ */ new Set();
|
|
13397
|
+
const bufferedLegacyCitations = [];
|
|
13398
|
+
let usage;
|
|
13399
|
+
let terminalStatus;
|
|
13400
|
+
let lastRaw;
|
|
13401
|
+
for await (const chunk of stream2) {
|
|
13402
|
+
lastRaw = chunk;
|
|
13403
|
+
if (!createdEmitted) {
|
|
13404
|
+
createdEmitted = true;
|
|
13405
|
+
yield { type: "created", jobId: null, rawEvent: chunk };
|
|
13406
|
+
yield { type: "status", status: "in_progress" };
|
|
13407
|
+
}
|
|
13408
|
+
const choice = chunk.choices?.[0];
|
|
13409
|
+
const delta = choice?.delta ?? {};
|
|
13410
|
+
const detailTexts = (delta.reasoning_details ?? []).filter((detail) => typeof detail.text === "string" && detail.text.length > 0).map((detail) => detail.text);
|
|
13411
|
+
if (detailTexts.length > 0) {
|
|
13412
|
+
for (const text3 of detailTexts) {
|
|
13413
|
+
yield { type: "thinking", delta: text3, rawEvent: chunk };
|
|
13414
|
+
}
|
|
13415
|
+
} else if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
|
|
13416
|
+
yield { type: "thinking", delta: delta.reasoning, rawEvent: chunk };
|
|
13417
|
+
}
|
|
13418
|
+
if (typeof delta.content === "string" && delta.content.length > 0) {
|
|
13419
|
+
yield { type: "text", delta: delta.content, rawEvent: chunk };
|
|
13420
|
+
}
|
|
13421
|
+
const annotationSources = [
|
|
13422
|
+
...delta.annotations ?? [],
|
|
13423
|
+
...choice?.message?.annotations ?? []
|
|
13424
|
+
];
|
|
13425
|
+
for (const annotation of annotationSources) {
|
|
13426
|
+
if (annotation.type !== "url_citation" || !annotation.url_citation?.url) continue;
|
|
13427
|
+
const citation = {
|
|
13428
|
+
url: annotation.url_citation.url,
|
|
13429
|
+
title: annotation.url_citation.title,
|
|
13430
|
+
content: annotation.url_citation.content,
|
|
13431
|
+
startIndex: annotation.url_citation.start_index,
|
|
13432
|
+
endIndex: annotation.url_citation.end_index
|
|
13433
|
+
};
|
|
13434
|
+
const key = citationKey(citation);
|
|
13435
|
+
if (!seenCitations.has(key)) {
|
|
13436
|
+
seenCitations.add(key);
|
|
13437
|
+
seenCitationUrls.add(citation.url);
|
|
13438
|
+
yield { type: "citation", citation, rawEvent: chunk };
|
|
13439
|
+
}
|
|
13440
|
+
}
|
|
13441
|
+
const legacyCitations = chunk.citations;
|
|
13442
|
+
if (Array.isArray(legacyCitations)) {
|
|
13443
|
+
for (const url of legacyCitations) {
|
|
13444
|
+
bufferedLegacyCitations.push({ url, rawEvent: chunk });
|
|
13445
|
+
}
|
|
13446
|
+
}
|
|
13447
|
+
if (choice?.finish_reason) {
|
|
13448
|
+
terminalStatus = mapFinishReason(choice.finish_reason);
|
|
13449
|
+
}
|
|
13450
|
+
const chunkUsage = chunk.usage;
|
|
13451
|
+
if (chunkUsage) {
|
|
13452
|
+
usage = {
|
|
13453
|
+
inputTokens: chunkUsage.prompt_tokens ?? 0,
|
|
13454
|
+
outputTokens: chunkUsage.completion_tokens ?? 0,
|
|
13455
|
+
totalTokens: chunkUsage.total_tokens ?? 0,
|
|
13456
|
+
cachedInputTokens: chunkUsage.prompt_tokens_details?.cached_tokens ?? void 0,
|
|
13457
|
+
reasoningTokens: chunkUsage.completion_tokens_details?.reasoning_tokens ?? void 0,
|
|
13458
|
+
searches: chunkUsage.num_search_queries ?? chunkUsage.server_tool_use?.web_search_requests ?? void 0,
|
|
13459
|
+
// Authoritative billed cost from OpenRouter usage accounting —
|
|
13460
|
+
// preferred over the catalog estimate (collector keeps it).
|
|
13461
|
+
costUSD: typeof chunkUsage.cost === "number" ? chunkUsage.cost : void 0
|
|
13462
|
+
};
|
|
13463
|
+
}
|
|
13464
|
+
}
|
|
13465
|
+
for (const { url, rawEvent } of bufferedLegacyCitations) {
|
|
13466
|
+
if (seenCitationUrls.has(url)) continue;
|
|
13467
|
+
seenCitationUrls.add(url);
|
|
13468
|
+
yield { type: "citation", citation: { url }, rawEvent };
|
|
13469
|
+
}
|
|
13470
|
+
if (usage) {
|
|
13471
|
+
yield { type: "usage", usage };
|
|
13472
|
+
}
|
|
13473
|
+
yield {
|
|
13474
|
+
type: "done",
|
|
13475
|
+
result: {
|
|
13476
|
+
status: terminalStatus ?? "completed",
|
|
13477
|
+
// Report text was streamed as deltas; the collector joins them.
|
|
13478
|
+
report: "",
|
|
13479
|
+
usage,
|
|
13480
|
+
raw: lastRaw
|
|
13481
|
+
}
|
|
13482
|
+
};
|
|
13483
|
+
}
|
|
13484
|
+
var init_openrouter_research = __esm({
|
|
13485
|
+
"src/providers/openrouter-research.ts"() {
|
|
13486
|
+
"use strict";
|
|
13487
|
+
init_errors2();
|
|
13488
|
+
}
|
|
13489
|
+
});
|
|
13490
|
+
|
|
13491
|
+
// src/providers/openrouter-research-models.ts
|
|
13492
|
+
function isOpenRouterResearchModel(modelId) {
|
|
13493
|
+
return byId3.has(modelId);
|
|
13494
|
+
}
|
|
13495
|
+
var OPENROUTER_STREAM_ONLY, openrouterResearchModels, byId3;
|
|
13496
|
+
var init_openrouter_research_models = __esm({
|
|
13497
|
+
"src/providers/openrouter-research-models.ts"() {
|
|
13498
|
+
"use strict";
|
|
13499
|
+
OPENROUTER_STREAM_ONLY = {
|
|
13500
|
+
streaming: true,
|
|
13501
|
+
background: false,
|
|
13502
|
+
resumable: false,
|
|
13503
|
+
tools: []
|
|
13504
|
+
};
|
|
13505
|
+
openrouterResearchModels = [
|
|
13506
|
+
{
|
|
13507
|
+
provider: "openrouter",
|
|
13508
|
+
modelId: "perplexity/sonar-deep-research",
|
|
13509
|
+
kind: "model",
|
|
13510
|
+
displayName: "Perplexity Sonar Deep Research",
|
|
13511
|
+
contextWindow: 128e3,
|
|
13512
|
+
pricing: {
|
|
13513
|
+
input: 2,
|
|
13514
|
+
output: 8,
|
|
13515
|
+
// Internal reasoning tokens are priced separately from output.
|
|
13516
|
+
internalReasoning: 3,
|
|
13517
|
+
perThousandSearches: 5
|
|
13518
|
+
},
|
|
13519
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
13520
|
+
metadata: {
|
|
13521
|
+
notes: "R1-style think-then-write: expect a long reasoning stream (100k+ tokens) before report text."
|
|
13522
|
+
}
|
|
13523
|
+
},
|
|
13524
|
+
{
|
|
13525
|
+
provider: "openrouter",
|
|
13526
|
+
modelId: "perplexity/sonar-pro-search",
|
|
13527
|
+
kind: "model",
|
|
13528
|
+
displayName: "Perplexity Sonar Pro Search",
|
|
13529
|
+
contextWindow: 2e5,
|
|
13530
|
+
pricing: { input: 3, output: 15, perThousandSearches: 18 },
|
|
13531
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
13532
|
+
metadata: { notes: "Agentic multi-step Pro Search \u2014 lighter than full deep research." }
|
|
13533
|
+
},
|
|
13534
|
+
{
|
|
13535
|
+
provider: "openrouter",
|
|
13536
|
+
modelId: "openai/o3-deep-research",
|
|
13537
|
+
kind: "model",
|
|
13538
|
+
displayName: "OpenAI o3 Deep Research (via OpenRouter)",
|
|
13539
|
+
contextWindow: 2e5,
|
|
13540
|
+
maxOutputTokens: 1e5,
|
|
13541
|
+
pricing: { input: 10, cachedInput: 2.5, output: 40, perThousandSearches: 10 },
|
|
13542
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
13543
|
+
metadata: {
|
|
13544
|
+
// Upstream OpenAI shutdown propagates through OpenRouter.
|
|
13545
|
+
deprecationDate: "2026-04-22",
|
|
13546
|
+
shutdownDate: "2026-07-23",
|
|
13547
|
+
replacement: "perplexity/sonar-deep-research",
|
|
13548
|
+
notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
|
|
13549
|
+
}
|
|
13550
|
+
},
|
|
13551
|
+
{
|
|
13552
|
+
provider: "openrouter",
|
|
13553
|
+
modelId: "openai/o4-mini-deep-research",
|
|
13554
|
+
kind: "model",
|
|
13555
|
+
displayName: "OpenAI o4-mini Deep Research (via OpenRouter)",
|
|
13556
|
+
contextWindow: 2e5,
|
|
13557
|
+
maxOutputTokens: 1e5,
|
|
13558
|
+
pricing: { input: 2, cachedInput: 0.5, output: 8, perThousandSearches: 10 },
|
|
13559
|
+
capabilities: OPENROUTER_STREAM_ONLY,
|
|
13560
|
+
metadata: {
|
|
13561
|
+
deprecationDate: "2026-04-22",
|
|
13562
|
+
shutdownDate: "2026-07-23",
|
|
13563
|
+
replacement: "perplexity/sonar-deep-research",
|
|
13564
|
+
notes: "Tools are managed upstream \u2014 requests carry no tools param via OpenRouter."
|
|
13565
|
+
}
|
|
13566
|
+
}
|
|
13567
|
+
];
|
|
13568
|
+
byId3 = new Map(openrouterResearchModels.map((spec) => [spec.modelId, spec]));
|
|
13569
|
+
}
|
|
13570
|
+
});
|
|
13571
|
+
|
|
12118
13572
|
// src/providers/openrouter-speech-models.ts
|
|
12119
13573
|
function getOpenRouterSpeechModelSpec(modelId) {
|
|
12120
13574
|
return openrouterSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -12242,8 +13696,11 @@ var init_openrouter = __esm({
|
|
|
12242
13696
|
import_openai4 = __toESM(require("openai"), 1);
|
|
12243
13697
|
init_model_shortcuts();
|
|
12244
13698
|
init_logger();
|
|
13699
|
+
init_constants3();
|
|
12245
13700
|
init_openai_compatible_provider();
|
|
12246
13701
|
init_openrouter_models();
|
|
13702
|
+
init_openrouter_research();
|
|
13703
|
+
init_openrouter_research_models();
|
|
12247
13704
|
init_openrouter_speech_models();
|
|
12248
13705
|
init_utils();
|
|
12249
13706
|
logger = createLogger({ name: "openrouter" });
|
|
@@ -12263,12 +13720,72 @@ var init_openrouter = __esm({
|
|
|
12263
13720
|
getModelSpecs() {
|
|
12264
13721
|
return OPENROUTER_MODELS;
|
|
12265
13722
|
}
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
13723
|
+
// =========================================================================
|
|
13724
|
+
// Deep Research (chat-completions surface)
|
|
13725
|
+
//
|
|
13726
|
+
// Stream-only: no background mode, no job id, no resume. resumeResearch /
|
|
13727
|
+
// getResearchStatus / cancelResearch are deliberately NOT implemented — the
|
|
13728
|
+
// research job surfaces typed errors for those operations, and a dropped
|
|
13729
|
+
// stream is never silently re-run (a multi-dollar research run).
|
|
13730
|
+
// =========================================================================
|
|
13731
|
+
getResearchModelSpecs() {
|
|
13732
|
+
return openrouterResearchModels;
|
|
13733
|
+
}
|
|
13734
|
+
supportsResearch(modelId) {
|
|
13735
|
+
return isOpenRouterResearchModel(modelId);
|
|
13736
|
+
}
|
|
13737
|
+
startResearch(options, descriptor, _spec) {
|
|
13738
|
+
const { messages } = buildOpenRouterResearchMessages(options);
|
|
13739
|
+
const client = this.client;
|
|
13740
|
+
const request = {
|
|
13741
|
+
model: descriptor.name,
|
|
13742
|
+
messages,
|
|
13743
|
+
// Mandatory: multi-minute runs hit idle disconnects without streaming.
|
|
13744
|
+
stream: true,
|
|
13745
|
+
stream_options: { include_usage: true },
|
|
13746
|
+
// OpenRouter usage accounting: report the authoritative billed cost
|
|
13747
|
+
// (covers per-search fees that token-based estimation cannot see).
|
|
13748
|
+
usage: { include: true }
|
|
13749
|
+
};
|
|
13750
|
+
if (options.reasoning?.enabled !== void 0) {
|
|
13751
|
+
request.reasoning = {
|
|
13752
|
+
effort: OPENROUTER_EFFORT_MAP[options.reasoning.effort ?? "medium"]
|
|
13753
|
+
};
|
|
13754
|
+
}
|
|
13755
|
+
const protectedKeys = /* @__PURE__ */ new Set(["model", "messages", "stream", "stream_options", "usage"]);
|
|
13756
|
+
Object.assign(request, this.buildProviderSpecificParams(options.extra));
|
|
13757
|
+
for (const [key, value] of Object.entries(options.extra ?? {})) {
|
|
13758
|
+
if (!this.isProviderSpecificKey(key) && !protectedKeys.has(key)) {
|
|
13759
|
+
request[key] = value;
|
|
13760
|
+
}
|
|
13761
|
+
}
|
|
13762
|
+
const headers = this.getCustomHeaders();
|
|
13763
|
+
const requestOptions3 = {
|
|
13764
|
+
signal: options.signal,
|
|
13765
|
+
// Research runs take minutes — override the client's default timeout.
|
|
13766
|
+
timeout: OPENROUTER_RESEARCH_HTTP_TIMEOUT_MS,
|
|
13767
|
+
...Object.keys(headers).length > 0 ? { headers } : {}
|
|
13768
|
+
};
|
|
13769
|
+
const enhance = (error) => this.enhanceError(error);
|
|
13770
|
+
return (async function* () {
|
|
13771
|
+
let stream2;
|
|
13772
|
+
try {
|
|
13773
|
+
stream2 = await client.chat.completions.create(
|
|
13774
|
+
request,
|
|
13775
|
+
requestOptions3
|
|
13776
|
+
);
|
|
13777
|
+
} catch (error) {
|
|
13778
|
+
throw enhance(error);
|
|
13779
|
+
}
|
|
13780
|
+
yield* normalizeOpenRouterResearchStream(stream2);
|
|
13781
|
+
})();
|
|
13782
|
+
}
|
|
13783
|
+
/**
|
|
13784
|
+
* Override buildApiRequest to inject reasoning parameters and cache_control breakpoints.
|
|
13785
|
+
* OpenRouter normalizes reasoning into the standard OpenAI format,
|
|
13786
|
+
* and supports cache_control on message content blocks for both
|
|
13787
|
+
* Anthropic Claude and Google Gemini models.
|
|
13788
|
+
*/
|
|
12272
13789
|
buildApiRequest(options, descriptor, spec, messages) {
|
|
12273
13790
|
const request = super.buildApiRequest(options, descriptor, spec, messages);
|
|
12274
13791
|
if (options.reasoning?.enabled !== void 0) {
|
|
@@ -12381,150 +13898,764 @@ Original error: ${error.message}`
|
|
|
12381
13898
|
Original error: ${error.message}`
|
|
12382
13899
|
);
|
|
12383
13900
|
}
|
|
12384
|
-
if (message.includes("503") || message.includes("unavailable")) {
|
|
12385
|
-
return new Error(
|
|
12386
|
-
`OpenRouter: Model temporarily unavailable. Try a different model or use the 'models' fallback option for automatic retry.
|
|
12387
|
-
Original error: ${error.message}`
|
|
13901
|
+
if (message.includes("503") || message.includes("unavailable")) {
|
|
13902
|
+
return new Error(
|
|
13903
|
+
`OpenRouter: Model temporarily unavailable. Try a different model or use the 'models' fallback option for automatic retry.
|
|
13904
|
+
Original error: ${error.message}`
|
|
13905
|
+
);
|
|
13906
|
+
}
|
|
13907
|
+
if (message.includes("400") || message.includes("bad request")) {
|
|
13908
|
+
const enhanced = new Error(
|
|
13909
|
+
`OpenRouter: Provider returned error (400). This may indicate the request exceeded the model's limits, or a model-specific rejection.
|
|
13910
|
+
Original error: ${error.message}`
|
|
13911
|
+
);
|
|
13912
|
+
const originalStatus = error.status;
|
|
13913
|
+
Object.assign(enhanced, {
|
|
13914
|
+
status: typeof originalStatus === "number" ? originalStatus : 400
|
|
13915
|
+
});
|
|
13916
|
+
return enhanced;
|
|
13917
|
+
}
|
|
13918
|
+
if (message.includes("401") || message.includes("unauthorized") || message.includes("invalid api key") || message.includes("invalid key") || message.includes("invalid_api_key")) {
|
|
13919
|
+
return new Error(
|
|
13920
|
+
`OpenRouter: Authentication failed. Check that OPENROUTER_API_KEY is set correctly.
|
|
13921
|
+
Original error: ${error.message}`
|
|
13922
|
+
);
|
|
13923
|
+
}
|
|
13924
|
+
return error;
|
|
13925
|
+
}
|
|
13926
|
+
// =========================================================================
|
|
13927
|
+
// Speech Generation (TTS via Chat Completions with Audio Modality)
|
|
13928
|
+
// =========================================================================
|
|
13929
|
+
/**
|
|
13930
|
+
* Get speech model specifications for OpenRouter.
|
|
13931
|
+
*/
|
|
13932
|
+
getSpeechModelSpecs() {
|
|
13933
|
+
return openrouterSpeechModels;
|
|
13934
|
+
}
|
|
13935
|
+
/**
|
|
13936
|
+
* Check if this provider supports speech generation for a given model.
|
|
13937
|
+
* Handles both prefixed (openrouter:openai/gpt-audio-mini) and unprefixed model IDs.
|
|
13938
|
+
*/
|
|
13939
|
+
supportsSpeechGeneration(modelId) {
|
|
13940
|
+
const bareModelId = getModelId(modelId);
|
|
13941
|
+
return isOpenRouterSpeechModel(bareModelId);
|
|
13942
|
+
}
|
|
13943
|
+
/**
|
|
13944
|
+
* Generate speech audio from text using OpenRouter's audio-capable models.
|
|
13945
|
+
*
|
|
13946
|
+
* OpenRouter TTS works via chat completions with audio modality, not a
|
|
13947
|
+
* dedicated TTS endpoint. The model receives a prompt asking it to say
|
|
13948
|
+
* the text, and returns audio data via streaming.
|
|
13949
|
+
*
|
|
13950
|
+
* @param options - Speech generation options
|
|
13951
|
+
* @returns Promise resolving to the generation result with audio and cost
|
|
13952
|
+
* @throws Error if model is unknown, voice/format are invalid, or no audio is returned
|
|
13953
|
+
*/
|
|
13954
|
+
async generateSpeech(options) {
|
|
13955
|
+
const client = this.client;
|
|
13956
|
+
const bareModelId = getModelId(options.model);
|
|
13957
|
+
const spec = getOpenRouterSpeechModelSpec(bareModelId);
|
|
13958
|
+
if (!spec) {
|
|
13959
|
+
throw new Error(`Unknown OpenRouter TTS model: ${options.model}`);
|
|
13960
|
+
}
|
|
13961
|
+
const voice = options.voice ?? spec.defaultVoice ?? "alloy";
|
|
13962
|
+
if (!spec.voices.includes(voice)) {
|
|
13963
|
+
throw new Error(
|
|
13964
|
+
`Invalid voice "${voice}" for ${options.model}. Valid voices: ${spec.voices.join(", ")}`
|
|
13965
|
+
);
|
|
13966
|
+
}
|
|
13967
|
+
const format2 = options.responseFormat ?? spec.defaultFormat ?? "mp3";
|
|
13968
|
+
if (!spec.formats.includes(format2)) {
|
|
13969
|
+
throw new Error(
|
|
13970
|
+
`Invalid format "${format2}" for ${options.model}. Valid formats: ${spec.formats.join(", ")}`
|
|
13971
|
+
);
|
|
13972
|
+
}
|
|
13973
|
+
logger.debug("TTS request", {
|
|
13974
|
+
model: bareModelId,
|
|
13975
|
+
voice,
|
|
13976
|
+
format: format2,
|
|
13977
|
+
inputLength: options.input.length
|
|
13978
|
+
});
|
|
13979
|
+
try {
|
|
13980
|
+
const response = await client.chat.completions.create({
|
|
13981
|
+
model: bareModelId,
|
|
13982
|
+
messages: [
|
|
13983
|
+
{
|
|
13984
|
+
role: "user",
|
|
13985
|
+
content: `Please say the following text exactly: "${options.input}"`
|
|
13986
|
+
}
|
|
13987
|
+
],
|
|
13988
|
+
// OpenRouter-specific parameters for audio output
|
|
13989
|
+
// Note: OpenRouter TTS via chat completions does NOT support the speed parameter
|
|
13990
|
+
// (unlike OpenAI's dedicated /audio/speech endpoint which does)
|
|
13991
|
+
modalities: ["text", "audio"],
|
|
13992
|
+
audio: {
|
|
13993
|
+
voice,
|
|
13994
|
+
format: format2
|
|
13995
|
+
},
|
|
13996
|
+
stream: true
|
|
13997
|
+
});
|
|
13998
|
+
const audioChunks = [];
|
|
13999
|
+
for await (const chunk of response) {
|
|
14000
|
+
const delta = chunk.choices[0]?.delta;
|
|
14001
|
+
if (!delta || typeof delta !== "object") continue;
|
|
14002
|
+
const audioObj = delta.audio;
|
|
14003
|
+
if (!audioObj || typeof audioObj !== "object") continue;
|
|
14004
|
+
const audioData = audioObj.data;
|
|
14005
|
+
if (typeof audioData !== "string" || audioData.length === 0) continue;
|
|
14006
|
+
const decoded = Buffer.from(audioData, "base64");
|
|
14007
|
+
if (decoded.length === 0) {
|
|
14008
|
+
throw new Error("Invalid base64 audio data received from OpenRouter");
|
|
14009
|
+
}
|
|
14010
|
+
audioChunks.push(decoded);
|
|
14011
|
+
}
|
|
14012
|
+
const audioBuffer = Buffer.concat(audioChunks);
|
|
14013
|
+
if (audioBuffer.length === 0) {
|
|
14014
|
+
throw new Error(
|
|
14015
|
+
"OpenRouter TTS returned no audio data. The model may have failed to generate audio or the stream was interrupted."
|
|
14016
|
+
);
|
|
14017
|
+
}
|
|
14018
|
+
const cost = calculateOpenRouterSpeechCost(bareModelId, options.input.length);
|
|
14019
|
+
return {
|
|
14020
|
+
// Use Uint8Array for clean ArrayBuffer extraction (safer than buffer.slice for Node.js Buffer)
|
|
14021
|
+
audio: new Uint8Array(audioBuffer).buffer,
|
|
14022
|
+
model: options.model,
|
|
14023
|
+
usage: {
|
|
14024
|
+
characterCount: options.input.length
|
|
14025
|
+
},
|
|
14026
|
+
cost,
|
|
14027
|
+
format: format2
|
|
14028
|
+
};
|
|
14029
|
+
} catch (error) {
|
|
14030
|
+
const err = error;
|
|
14031
|
+
const apiError = err.error?.message || err.error?.code || "";
|
|
14032
|
+
const bodyInfo = err.body ? JSON.stringify(err.body) : "";
|
|
14033
|
+
const details = apiError || bodyInfo || err.message || "Unknown error";
|
|
14034
|
+
logger.debug("TTS error", {
|
|
14035
|
+
model: bareModelId,
|
|
14036
|
+
voice,
|
|
14037
|
+
format: format2,
|
|
14038
|
+
status: err.status,
|
|
14039
|
+
error: err.error,
|
|
14040
|
+
body: err.body,
|
|
14041
|
+
message: err.message
|
|
14042
|
+
});
|
|
14043
|
+
throw new Error(
|
|
14044
|
+
`OpenRouter TTS failed for model ${bareModelId}: ${details}` + (err.status ? ` (HTTP ${err.status})` : "")
|
|
14045
|
+
);
|
|
14046
|
+
}
|
|
14047
|
+
}
|
|
14048
|
+
};
|
|
14049
|
+
}
|
|
14050
|
+
});
|
|
14051
|
+
|
|
14052
|
+
// src/providers/discovery.ts
|
|
14053
|
+
function discoverProviderAdapters() {
|
|
14054
|
+
const adapters = [];
|
|
14055
|
+
for (const discover of DISCOVERERS) {
|
|
14056
|
+
const adapter = discover();
|
|
14057
|
+
if (adapter) {
|
|
14058
|
+
adapters.push(adapter);
|
|
14059
|
+
}
|
|
14060
|
+
}
|
|
14061
|
+
return adapters;
|
|
14062
|
+
}
|
|
14063
|
+
var DISCOVERERS;
|
|
14064
|
+
var init_discovery = __esm({
|
|
14065
|
+
"src/providers/discovery.ts"() {
|
|
14066
|
+
"use strict";
|
|
14067
|
+
init_anthropic();
|
|
14068
|
+
init_gemini();
|
|
14069
|
+
init_huggingface();
|
|
14070
|
+
init_openai();
|
|
14071
|
+
init_openrouter();
|
|
14072
|
+
DISCOVERERS = [
|
|
14073
|
+
createOpenAIProviderFromEnv,
|
|
14074
|
+
createAnthropicProviderFromEnv,
|
|
14075
|
+
createGeminiProviderFromEnv,
|
|
14076
|
+
createHuggingFaceProviderFromEnv,
|
|
14077
|
+
createOpenRouterProviderFromEnv
|
|
14078
|
+
];
|
|
14079
|
+
}
|
|
14080
|
+
});
|
|
14081
|
+
|
|
14082
|
+
// src/research/cost.ts
|
|
14083
|
+
function estimateResearchCost(pricing, usage) {
|
|
14084
|
+
const cachedTokens = usage.cachedInputTokens ?? 0;
|
|
14085
|
+
const freshInputTokens = Math.max(0, usage.inputTokens - cachedTokens);
|
|
14086
|
+
const cachedRate = pricing.cachedInput ?? pricing.input;
|
|
14087
|
+
let outputTokens = usage.outputTokens;
|
|
14088
|
+
let reasoningCost = 0;
|
|
14089
|
+
if (pricing.internalReasoning !== void 0 && usage.reasoningTokens !== void 0) {
|
|
14090
|
+
outputTokens = Math.max(0, outputTokens - usage.reasoningTokens);
|
|
14091
|
+
reasoningCost = usage.reasoningTokens * pricing.internalReasoning / TOKENS_PER_MILLION;
|
|
14092
|
+
}
|
|
14093
|
+
const searchCost = pricing.perThousandSearches !== void 0 && usage.searches !== void 0 ? usage.searches * pricing.perThousandSearches / SEARCHES_PER_THOUSAND : 0;
|
|
14094
|
+
const total = freshInputTokens * pricing.input / TOKENS_PER_MILLION + cachedTokens * cachedRate / TOKENS_PER_MILLION + outputTokens * pricing.output / TOKENS_PER_MILLION + reasoningCost + searchCost;
|
|
14095
|
+
return Number(total.toFixed(RESEARCH_COST_DECIMALS));
|
|
14096
|
+
}
|
|
14097
|
+
var init_cost = __esm({
|
|
14098
|
+
"src/research/cost.ts"() {
|
|
14099
|
+
"use strict";
|
|
14100
|
+
init_constants3();
|
|
14101
|
+
}
|
|
14102
|
+
});
|
|
14103
|
+
|
|
14104
|
+
// src/research/collector.ts
|
|
14105
|
+
function citationKey2(citation) {
|
|
14106
|
+
return `${citation.url}#${citation.startIndex ?? ""}`;
|
|
14107
|
+
}
|
|
14108
|
+
function max(a, b) {
|
|
14109
|
+
if (a === void 0) return b;
|
|
14110
|
+
if (b === void 0) return a;
|
|
14111
|
+
return Math.max(a, b);
|
|
14112
|
+
}
|
|
14113
|
+
var EMPTY_USAGE, TERMINAL_STATUSES, ResearchResultCollector;
|
|
14114
|
+
var init_collector = __esm({
|
|
14115
|
+
"src/research/collector.ts"() {
|
|
14116
|
+
"use strict";
|
|
14117
|
+
init_cost();
|
|
14118
|
+
EMPTY_USAGE = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
14119
|
+
TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
14120
|
+
"completed",
|
|
14121
|
+
"failed",
|
|
14122
|
+
"cancelled",
|
|
14123
|
+
"incomplete",
|
|
14124
|
+
"budget_exceeded"
|
|
14125
|
+
]);
|
|
14126
|
+
ResearchResultCollector = class {
|
|
14127
|
+
constructor(spec, now = Date.now) {
|
|
14128
|
+
this.spec = spec;
|
|
14129
|
+
this.now = now;
|
|
14130
|
+
}
|
|
14131
|
+
spec;
|
|
14132
|
+
now;
|
|
14133
|
+
reportParts = [];
|
|
14134
|
+
citations = /* @__PURE__ */ new Map();
|
|
14135
|
+
usage;
|
|
14136
|
+
lastStatus;
|
|
14137
|
+
doneReport = "";
|
|
14138
|
+
doneRaw;
|
|
14139
|
+
hasDone = false;
|
|
14140
|
+
firstEventAt;
|
|
14141
|
+
terminalAt;
|
|
14142
|
+
/**
|
|
14143
|
+
* Terminal error info from an `error` event.
|
|
14144
|
+
* @internal Read by tests only — not part of the public consumer contract.
|
|
14145
|
+
*/
|
|
14146
|
+
terminalError;
|
|
14147
|
+
ingest(event) {
|
|
14148
|
+
this.firstEventAt ??= this.now();
|
|
14149
|
+
switch (event.type) {
|
|
14150
|
+
case "text":
|
|
14151
|
+
this.reportParts.push(event.delta);
|
|
14152
|
+
break;
|
|
14153
|
+
case "citation":
|
|
14154
|
+
this.addCitation(event.citation);
|
|
14155
|
+
break;
|
|
14156
|
+
case "usage":
|
|
14157
|
+
this.mergeUsage(event.usage);
|
|
14158
|
+
break;
|
|
14159
|
+
case "status":
|
|
14160
|
+
this.lastStatus = event.status;
|
|
14161
|
+
break;
|
|
14162
|
+
case "error":
|
|
14163
|
+
this.terminalError = event.error;
|
|
14164
|
+
this.terminalAt = this.now();
|
|
14165
|
+
break;
|
|
14166
|
+
case "done": {
|
|
14167
|
+
this.hasDone = true;
|
|
14168
|
+
this.lastStatus = event.result.status;
|
|
14169
|
+
this.doneReport = event.result.report;
|
|
14170
|
+
this.doneRaw = event.result.raw;
|
|
14171
|
+
for (const citation of event.result.citations ?? []) {
|
|
14172
|
+
this.addCitation(citation);
|
|
14173
|
+
}
|
|
14174
|
+
if (event.result.usage) {
|
|
14175
|
+
this.mergeUsage(event.result.usage);
|
|
14176
|
+
}
|
|
14177
|
+
this.terminalAt = this.now();
|
|
14178
|
+
break;
|
|
14179
|
+
}
|
|
14180
|
+
default:
|
|
14181
|
+
break;
|
|
14182
|
+
}
|
|
14183
|
+
}
|
|
14184
|
+
toResult(context) {
|
|
14185
|
+
const usage = { ...this.usage ?? EMPTY_USAGE };
|
|
14186
|
+
if (usage.costUSD === void 0 && this.spec) {
|
|
14187
|
+
usage.costUSD = estimateResearchCost(this.spec.pricing, usage);
|
|
14188
|
+
}
|
|
14189
|
+
const status = this.lastStatus ?? (this.terminalError ? "failed" : "in_progress");
|
|
14190
|
+
const lastIsTerminal = this.lastStatus !== void 0 && TERMINAL_STATUSES.has(this.lastStatus);
|
|
14191
|
+
return {
|
|
14192
|
+
jobId: context.jobId,
|
|
14193
|
+
provider: context.provider,
|
|
14194
|
+
model: context.model,
|
|
14195
|
+
// An error fails the run only when no explicit terminal status preceded
|
|
14196
|
+
// it (a timeout's "incomplete" is a partial, not a failure).
|
|
14197
|
+
status: this.terminalError && !this.hasDone && !lastIsTerminal ? "failed" : status,
|
|
14198
|
+
report: this.doneReport !== "" ? this.doneReport : this.reportParts.join(""),
|
|
14199
|
+
citations: [...this.citations.values()],
|
|
14200
|
+
usage,
|
|
14201
|
+
durationMs: this.firstEventAt !== void 0 && this.terminalAt !== void 0 ? this.terminalAt - this.firstEventAt : void 0,
|
|
14202
|
+
raw: this.doneRaw
|
|
14203
|
+
};
|
|
14204
|
+
}
|
|
14205
|
+
/** Whether a terminal event (`done` or `error`) was ingested. */
|
|
14206
|
+
get isTerminal() {
|
|
14207
|
+
return this.hasDone || this.terminalError !== void 0;
|
|
14208
|
+
}
|
|
14209
|
+
addCitation(citation) {
|
|
14210
|
+
const key = citationKey2(citation);
|
|
14211
|
+
if (!this.citations.has(key)) {
|
|
14212
|
+
this.citations.set(key, citation);
|
|
14213
|
+
}
|
|
14214
|
+
}
|
|
14215
|
+
mergeUsage(incoming) {
|
|
14216
|
+
const current = this.usage ?? { ...EMPTY_USAGE };
|
|
14217
|
+
this.usage = {
|
|
14218
|
+
...current,
|
|
14219
|
+
...incoming,
|
|
14220
|
+
// Cumulative counters may be re-reported; never let them regress.
|
|
14221
|
+
searches: max(current.searches, incoming.searches)
|
|
14222
|
+
};
|
|
14223
|
+
}
|
|
14224
|
+
};
|
|
14225
|
+
}
|
|
14226
|
+
});
|
|
14227
|
+
|
|
14228
|
+
// src/research/job.ts
|
|
14229
|
+
var ResearchJobImpl;
|
|
14230
|
+
var init_job = __esm({
|
|
14231
|
+
"src/research/job.ts"() {
|
|
14232
|
+
"use strict";
|
|
14233
|
+
init_errors();
|
|
14234
|
+
init_collector();
|
|
14235
|
+
init_constants3();
|
|
14236
|
+
init_errors2();
|
|
14237
|
+
ResearchJobImpl = class {
|
|
14238
|
+
adapter;
|
|
14239
|
+
descriptor;
|
|
14240
|
+
spec;
|
|
14241
|
+
options;
|
|
14242
|
+
resumeFrom;
|
|
14243
|
+
collector;
|
|
14244
|
+
controller = new AbortController();
|
|
14245
|
+
timeoutMs;
|
|
14246
|
+
state = "idle";
|
|
14247
|
+
currentJobId;
|
|
14248
|
+
cursor;
|
|
14249
|
+
startedAt;
|
|
14250
|
+
timedOut = false;
|
|
14251
|
+
cancelRequested = false;
|
|
14252
|
+
finalResult;
|
|
14253
|
+
failure;
|
|
14254
|
+
completion;
|
|
14255
|
+
constructor(init) {
|
|
14256
|
+
this.adapter = init.adapter;
|
|
14257
|
+
this.descriptor = init.descriptor;
|
|
14258
|
+
this.spec = init.spec;
|
|
14259
|
+
this.options = init.options;
|
|
14260
|
+
this.resumeFrom = init.resumeFrom;
|
|
14261
|
+
this.collector = new ResearchResultCollector(init.spec);
|
|
14262
|
+
this.currentJobId = init.resumeFrom?.jobId ?? null;
|
|
14263
|
+
this.cursor = init.resumeFrom?.cursor;
|
|
14264
|
+
this.startedAt = init.resumeFrom?.startedAt;
|
|
14265
|
+
const specCap = init.spec?.maxDurationMs ?? Number.POSITIVE_INFINITY;
|
|
14266
|
+
this.timeoutMs = init.options?.timeoutMs ?? Math.min(RESEARCH_DEFAULT_TIMEOUT_MS, specCap);
|
|
14267
|
+
if (init.options?.signal) {
|
|
14268
|
+
const external = init.options.signal;
|
|
14269
|
+
if (external.aborted) {
|
|
14270
|
+
this.controller.abort(external.reason);
|
|
14271
|
+
} else {
|
|
14272
|
+
external.addEventListener("abort", () => this.controller.abort(external.reason), {
|
|
14273
|
+
once: true
|
|
14274
|
+
});
|
|
14275
|
+
}
|
|
14276
|
+
}
|
|
14277
|
+
}
|
|
14278
|
+
get jobId() {
|
|
14279
|
+
return this.currentJobId;
|
|
14280
|
+
}
|
|
14281
|
+
get provider() {
|
|
14282
|
+
return this.adapter.providerId;
|
|
14283
|
+
}
|
|
14284
|
+
get model() {
|
|
14285
|
+
return this.descriptor.name;
|
|
14286
|
+
}
|
|
14287
|
+
[Symbol.asyncIterator]() {
|
|
14288
|
+
return this.events()[Symbol.asyncIterator]();
|
|
14289
|
+
}
|
|
14290
|
+
events() {
|
|
14291
|
+
if (this.state !== "idle") {
|
|
14292
|
+
throw new ResearchStreamConsumedError();
|
|
14293
|
+
}
|
|
14294
|
+
this.state = "streaming";
|
|
14295
|
+
let resolveCompletion = () => {
|
|
14296
|
+
};
|
|
14297
|
+
const promise = new Promise((resolve2) => {
|
|
14298
|
+
resolveCompletion = resolve2;
|
|
14299
|
+
});
|
|
14300
|
+
this.completion = { promise, resolve: resolveCompletion };
|
|
14301
|
+
return this.run();
|
|
14302
|
+
}
|
|
14303
|
+
async result() {
|
|
14304
|
+
if (this.state === "idle") {
|
|
14305
|
+
for await (const _event of this.events()) {
|
|
14306
|
+
}
|
|
14307
|
+
} else if (this.state === "streaming" && this.completion) {
|
|
14308
|
+
await this.completion.promise;
|
|
14309
|
+
}
|
|
14310
|
+
if (this.failure) {
|
|
14311
|
+
throw this.failure;
|
|
14312
|
+
}
|
|
14313
|
+
this.finalResult ??= this.collector.toResult(this.resultContext());
|
|
14314
|
+
return this.finalResult;
|
|
14315
|
+
}
|
|
14316
|
+
async status() {
|
|
14317
|
+
if (!this.adapter.getResearchStatus) {
|
|
14318
|
+
throw new ResearchNotPollableError(
|
|
14319
|
+
`Provider "${this.provider}" does not support research status polling.`
|
|
14320
|
+
);
|
|
14321
|
+
}
|
|
14322
|
+
if (this.currentJobId === null) {
|
|
14323
|
+
throw new ResearchNotPollableError(
|
|
14324
|
+
`Research run on "${this.provider}" has no server-side job id to poll.`
|
|
14325
|
+
);
|
|
14326
|
+
}
|
|
14327
|
+
const snapshot = await this.adapter.getResearchStatus(this.buildRef());
|
|
14328
|
+
return snapshot.status;
|
|
14329
|
+
}
|
|
14330
|
+
async cancel() {
|
|
14331
|
+
this.cancelRequested = true;
|
|
14332
|
+
if (this.adapter.cancelResearch && this.currentJobId !== null) {
|
|
14333
|
+
await this.adapter.cancelResearch(this.buildRef());
|
|
14334
|
+
}
|
|
14335
|
+
this.controller.abort();
|
|
14336
|
+
}
|
|
14337
|
+
toRef() {
|
|
14338
|
+
if (this.currentJobId === null) {
|
|
14339
|
+
throw new ResearchJobNotResumableError(
|
|
14340
|
+
`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.`
|
|
14341
|
+
);
|
|
14342
|
+
}
|
|
14343
|
+
return this.buildRef();
|
|
14344
|
+
}
|
|
14345
|
+
buildRef() {
|
|
14346
|
+
if (this.currentJobId === null) {
|
|
14347
|
+
throw new ResearchJobNotResumableError("Research job has no server-side job id.");
|
|
14348
|
+
}
|
|
14349
|
+
return {
|
|
14350
|
+
provider: this.provider,
|
|
14351
|
+
model: this.descriptor.name,
|
|
14352
|
+
jobId: this.currentJobId,
|
|
14353
|
+
cursor: this.cursor,
|
|
14354
|
+
startedAt: this.startedAt
|
|
14355
|
+
};
|
|
14356
|
+
}
|
|
14357
|
+
resultContext() {
|
|
14358
|
+
return { provider: this.provider, model: this.descriptor.name, jobId: this.currentJobId };
|
|
14359
|
+
}
|
|
14360
|
+
openStream(resume) {
|
|
14361
|
+
if (resume) {
|
|
14362
|
+
if (!this.adapter.resumeResearch) {
|
|
14363
|
+
throw new ResearchJobNotResumableError(
|
|
14364
|
+
`Provider "${this.provider}" does not support resuming research jobs.`
|
|
14365
|
+
);
|
|
14366
|
+
}
|
|
14367
|
+
return this.adapter.resumeResearch(this.buildRef(), this.controller.signal);
|
|
14368
|
+
}
|
|
14369
|
+
if (!this.adapter.startResearch || !this.options) {
|
|
14370
|
+
throw new ResearchJobNotResumableError(
|
|
14371
|
+
`Provider "${this.provider}" cannot start this research job.`
|
|
14372
|
+
);
|
|
14373
|
+
}
|
|
14374
|
+
return this.adapter.startResearch(
|
|
14375
|
+
{ ...this.options, signal: this.controller.signal },
|
|
14376
|
+
this.descriptor,
|
|
14377
|
+
this.spec
|
|
14378
|
+
);
|
|
14379
|
+
}
|
|
14380
|
+
observe(event) {
|
|
14381
|
+
if (event.cursor !== void 0) {
|
|
14382
|
+
this.cursor = event.cursor;
|
|
14383
|
+
}
|
|
14384
|
+
if (event.type === "created") {
|
|
14385
|
+
if (event.jobId !== null) {
|
|
14386
|
+
this.currentJobId = event.jobId;
|
|
14387
|
+
}
|
|
14388
|
+
this.startedAt ??= (/* @__PURE__ */ new Date()).toISOString();
|
|
14389
|
+
}
|
|
14390
|
+
this.collector.ingest(event);
|
|
14391
|
+
}
|
|
14392
|
+
canResume() {
|
|
14393
|
+
if (this.adapter.resumeResearch === void 0 || this.currentJobId === null) {
|
|
14394
|
+
return false;
|
|
14395
|
+
}
|
|
14396
|
+
return this.spec ? this.spec.capabilities.resumable : true;
|
|
14397
|
+
}
|
|
14398
|
+
async *run() {
|
|
14399
|
+
const timer = setTimeout(() => {
|
|
14400
|
+
this.timedOut = true;
|
|
14401
|
+
this.controller.abort(new ResearchTimeoutError(this.timeoutMs));
|
|
14402
|
+
}, this.timeoutMs);
|
|
14403
|
+
timer.unref?.();
|
|
14404
|
+
let reconnectAttempts = 0;
|
|
14405
|
+
let source = this.openStream(this.resumeFrom !== void 0);
|
|
14406
|
+
try {
|
|
14407
|
+
while (true) {
|
|
14408
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
14409
|
+
try {
|
|
14410
|
+
while (true) {
|
|
14411
|
+
const next = await iterator.next();
|
|
14412
|
+
if (next.done) {
|
|
14413
|
+
return;
|
|
14414
|
+
}
|
|
14415
|
+
reconnectAttempts = 0;
|
|
14416
|
+
this.observe(next.value);
|
|
14417
|
+
yield next.value;
|
|
14418
|
+
if (this.collector.isTerminal) {
|
|
14419
|
+
return;
|
|
14420
|
+
}
|
|
14421
|
+
}
|
|
14422
|
+
} catch (error) {
|
|
14423
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
14424
|
+
if (this.timedOut) {
|
|
14425
|
+
const statusEvent = { type: "status", status: "incomplete" };
|
|
14426
|
+
this.collector.ingest(statusEvent);
|
|
14427
|
+
yield statusEvent;
|
|
14428
|
+
const info2 = {
|
|
14429
|
+
message: new ResearchTimeoutError(this.timeoutMs).message,
|
|
14430
|
+
code: "timeout",
|
|
14431
|
+
retryable: false
|
|
14432
|
+
};
|
|
14433
|
+
this.collector.ingest({ type: "error", error: info2 });
|
|
14434
|
+
yield { type: "error", error: info2 };
|
|
14435
|
+
return;
|
|
14436
|
+
}
|
|
14437
|
+
if (this.cancelRequested && isAbortError(err)) {
|
|
14438
|
+
const event = { type: "status", status: "cancelled" };
|
|
14439
|
+
this.observe(event);
|
|
14440
|
+
yield event;
|
|
14441
|
+
return;
|
|
14442
|
+
}
|
|
14443
|
+
if (isAbortError(err)) {
|
|
14444
|
+
this.failure = err;
|
|
14445
|
+
throw err;
|
|
14446
|
+
}
|
|
14447
|
+
if (this.canResume() && reconnectAttempts < RESEARCH_STREAM_RECONNECT_MAX_ATTEMPTS) {
|
|
14448
|
+
reconnectAttempts += 1;
|
|
14449
|
+
source = this.openStream(true);
|
|
14450
|
+
continue;
|
|
14451
|
+
}
|
|
14452
|
+
const info = { message: err.message, retryable: false };
|
|
14453
|
+
this.collector.ingest({ type: "error", error: info });
|
|
14454
|
+
yield { type: "error", error: info };
|
|
14455
|
+
return;
|
|
14456
|
+
}
|
|
14457
|
+
}
|
|
14458
|
+
} finally {
|
|
14459
|
+
clearTimeout(timer);
|
|
14460
|
+
this.state = "finished";
|
|
14461
|
+
this.finalResult = this.collector.toResult(this.resultContext());
|
|
14462
|
+
this.completion?.resolve();
|
|
14463
|
+
}
|
|
14464
|
+
}
|
|
14465
|
+
};
|
|
14466
|
+
}
|
|
14467
|
+
});
|
|
14468
|
+
|
|
14469
|
+
// src/research/types.ts
|
|
14470
|
+
var RESEARCH_DATA_SOURCE_TOOL_TYPES;
|
|
14471
|
+
var init_types = __esm({
|
|
14472
|
+
"src/research/types.ts"() {
|
|
14473
|
+
"use strict";
|
|
14474
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES = [
|
|
14475
|
+
"web_search",
|
|
14476
|
+
"file_search",
|
|
14477
|
+
"mcp"
|
|
14478
|
+
];
|
|
14479
|
+
}
|
|
14480
|
+
});
|
|
14481
|
+
|
|
14482
|
+
// src/research/namespace.ts
|
|
14483
|
+
var ResearchNamespace;
|
|
14484
|
+
var init_namespace = __esm({
|
|
14485
|
+
"src/research/namespace.ts"() {
|
|
14486
|
+
"use strict";
|
|
14487
|
+
init_logger();
|
|
14488
|
+
init_constants3();
|
|
14489
|
+
init_errors2();
|
|
14490
|
+
init_job();
|
|
14491
|
+
init_types();
|
|
14492
|
+
ResearchNamespace = class {
|
|
14493
|
+
constructor(adapters, parser, now = Date.now, logger2) {
|
|
14494
|
+
this.adapters = adapters;
|
|
14495
|
+
this.parser = parser;
|
|
14496
|
+
this.now = now;
|
|
14497
|
+
this.logger = logger2 ?? createLogger({ name: "llmist:research" });
|
|
14498
|
+
}
|
|
14499
|
+
adapters;
|
|
14500
|
+
parser;
|
|
14501
|
+
now;
|
|
14502
|
+
logger;
|
|
14503
|
+
/**
|
|
14504
|
+
* Start a research run. Returns immediately; the provider stream opens
|
|
14505
|
+
* lazily on first iteration (or on `result()`).
|
|
14506
|
+
*/
|
|
14507
|
+
start(options) {
|
|
14508
|
+
const descriptor = this.parser.parse(options.model);
|
|
14509
|
+
const adapter = this.findResearchAdapter(descriptor);
|
|
14510
|
+
if (!adapter) {
|
|
14511
|
+
throw new ResearchNotSupportedError(
|
|
14512
|
+
`No provider supports deep research for model "${options.model}". Research-capable models: ${this.describeAvailableModels()}`
|
|
14513
|
+
);
|
|
14514
|
+
}
|
|
14515
|
+
const spec = this.findSpec(adapter, descriptor.name);
|
|
14516
|
+
const validated = spec ? this.validate(options, spec) : options;
|
|
14517
|
+
return new ResearchJobImpl({ adapter, descriptor, spec, options: validated });
|
|
14518
|
+
}
|
|
14519
|
+
/**
|
|
14520
|
+
* Re-attach to a background research job from a serialized ref.
|
|
14521
|
+
* No network happens until the returned job is iterated.
|
|
14522
|
+
*/
|
|
14523
|
+
attach(ref) {
|
|
14524
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
14525
|
+
if (!adapter) {
|
|
14526
|
+
throw new ResearchNotSupportedError(
|
|
14527
|
+
`No registered provider with id "${ref.provider}" to attach research job "${ref.jobId}".`
|
|
14528
|
+
);
|
|
14529
|
+
}
|
|
14530
|
+
if (!adapter.resumeResearch) {
|
|
14531
|
+
throw new ResearchJobNotResumableError(
|
|
14532
|
+
`Provider "${ref.provider}" does not support resuming research jobs.`
|
|
12388
14533
|
);
|
|
12389
14534
|
}
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
14535
|
+
const descriptor = { provider: ref.provider, name: ref.model };
|
|
14536
|
+
const spec = this.findSpec(adapter, ref.model);
|
|
14537
|
+
return new ResearchJobImpl({ adapter, descriptor, spec, resumeFrom: ref });
|
|
14538
|
+
}
|
|
14539
|
+
/** One-shot status poll for a job ref. */
|
|
14540
|
+
async get(ref) {
|
|
14541
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
14542
|
+
if (!adapter) {
|
|
14543
|
+
throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
|
|
14544
|
+
}
|
|
14545
|
+
if (!adapter.getResearchStatus) {
|
|
14546
|
+
throw new ResearchNotPollableError(
|
|
14547
|
+
`Provider "${ref.provider}" does not support research status polling.`
|
|
12394
14548
|
);
|
|
12395
|
-
const originalStatus = error.status;
|
|
12396
|
-
Object.assign(enhanced, {
|
|
12397
|
-
status: typeof originalStatus === "number" ? originalStatus : 400
|
|
12398
|
-
});
|
|
12399
|
-
return enhanced;
|
|
12400
14549
|
}
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
14550
|
+
return adapter.getResearchStatus(ref);
|
|
14551
|
+
}
|
|
14552
|
+
/** Cancel a background job server-side. */
|
|
14553
|
+
async cancel(ref) {
|
|
14554
|
+
const adapter = this.findAdapterByProviderId(ref.provider);
|
|
14555
|
+
if (!adapter) {
|
|
14556
|
+
throw new ResearchNotSupportedError(`No registered provider with id "${ref.provider}".`);
|
|
14557
|
+
}
|
|
14558
|
+
if (!adapter.cancelResearch) {
|
|
14559
|
+
throw new ResearchNotSupportedError(
|
|
14560
|
+
`Provider "${ref.provider}" does not support cancelling research jobs.`
|
|
12405
14561
|
);
|
|
12406
14562
|
}
|
|
12407
|
-
return
|
|
14563
|
+
return adapter.cancelResearch(ref);
|
|
12408
14564
|
}
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
return openrouterSpeechModels;
|
|
14565
|
+
/** All research-capable models/agents across registered providers. */
|
|
14566
|
+
listModels() {
|
|
14567
|
+
const specs = [];
|
|
14568
|
+
for (const adapter of this.adapters) {
|
|
14569
|
+
specs.push(...adapter.getResearchModelSpecs?.() ?? []);
|
|
14570
|
+
}
|
|
14571
|
+
return specs;
|
|
12417
14572
|
}
|
|
12418
|
-
/**
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
14573
|
+
/** Whether any registered provider supports research for this model. */
|
|
14574
|
+
supportsModel(model) {
|
|
14575
|
+
try {
|
|
14576
|
+
return this.findResearchAdapter(this.parser.parse(model)) !== void 0;
|
|
14577
|
+
} catch {
|
|
14578
|
+
return false;
|
|
14579
|
+
}
|
|
14580
|
+
}
|
|
14581
|
+
findResearchAdapter(descriptor) {
|
|
14582
|
+
return this.adapters.find(
|
|
14583
|
+
(adapter) => adapter.supports(descriptor) && (adapter.supportsResearch?.(descriptor.name) ?? false)
|
|
14584
|
+
);
|
|
14585
|
+
}
|
|
14586
|
+
findAdapterByProviderId(providerId) {
|
|
14587
|
+
return this.adapters.find((adapter) => adapter.providerId === providerId);
|
|
14588
|
+
}
|
|
14589
|
+
findSpec(adapter, modelId) {
|
|
14590
|
+
return adapter.getResearchModelSpecs?.().find((spec) => spec.modelId === modelId);
|
|
14591
|
+
}
|
|
14592
|
+
describeAvailableModels() {
|
|
14593
|
+
const models = this.listModels();
|
|
14594
|
+
if (models.length === 0) {
|
|
14595
|
+
return "(none registered)";
|
|
14596
|
+
}
|
|
14597
|
+
return models.map((spec) => `${spec.provider}:${spec.modelId}`).join(", ");
|
|
12425
14598
|
}
|
|
12426
14599
|
/**
|
|
12427
|
-
*
|
|
12428
|
-
*
|
|
12429
|
-
* OpenRouter TTS works via chat completions with audio modality, not a
|
|
12430
|
-
* dedicated TTS endpoint. The model receives a prompt asking it to say
|
|
12431
|
-
* the text, and returns audio data via streaming.
|
|
12432
|
-
*
|
|
12433
|
-
* @param options - Speech generation options
|
|
12434
|
-
* @returns Promise resolving to the generation result with audio and cost
|
|
12435
|
-
* @throws Error if model is unknown, voice/format are invalid, or no audio is returned
|
|
14600
|
+
* Pre-flight validation against the catalog spec — fails fast before any
|
|
14601
|
+
* network call and applies spec-driven defaults.
|
|
12436
14602
|
*/
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
const
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
}
|
|
12444
|
-
const voice = options.voice ?? spec.defaultVoice ?? "alloy";
|
|
12445
|
-
if (!spec.voices.includes(voice)) {
|
|
12446
|
-
throw new Error(
|
|
12447
|
-
`Invalid voice "${voice}" for ${options.model}. Valid voices: ${spec.voices.join(", ")}`
|
|
14603
|
+
validate(options, spec) {
|
|
14604
|
+
this.enforceLifecycle(spec);
|
|
14605
|
+
const background = options.background ?? spec.capabilities.background;
|
|
14606
|
+
if (background && !spec.capabilities.background) {
|
|
14607
|
+
throw new ResearchValidationError(
|
|
14608
|
+
`Model "${spec.modelId}" does not support background research jobs.`
|
|
12448
14609
|
);
|
|
12449
14610
|
}
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
`Invalid format "${format2}" for ${options.model}. Valid formats: ${spec.formats.join(", ")}`
|
|
14611
|
+
if (options.previousJobId && !spec.capabilities.followUps) {
|
|
14612
|
+
throw new ResearchValidationError(
|
|
14613
|
+
`Model "${spec.modelId}" does not support follow-up research runs (previousJobId).`
|
|
12454
14614
|
);
|
|
12455
14615
|
}
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
{
|
|
12467
|
-
role: "user",
|
|
12468
|
-
content: `Please say the following text exactly: "${options.input}"`
|
|
12469
|
-
}
|
|
12470
|
-
],
|
|
12471
|
-
// OpenRouter-specific parameters for audio output
|
|
12472
|
-
// Note: OpenRouter TTS via chat completions does NOT support the speed parameter
|
|
12473
|
-
// (unlike OpenAI's dedicated /audio/speech endpoint which does)
|
|
12474
|
-
modalities: ["text", "audio"],
|
|
12475
|
-
audio: {
|
|
12476
|
-
voice,
|
|
12477
|
-
format: format2
|
|
12478
|
-
},
|
|
12479
|
-
stream: true
|
|
12480
|
-
});
|
|
12481
|
-
const audioChunks = [];
|
|
12482
|
-
for await (const chunk of response) {
|
|
12483
|
-
const delta = chunk.choices[0]?.delta;
|
|
12484
|
-
if (!delta || typeof delta !== "object") continue;
|
|
12485
|
-
const audioObj = delta.audio;
|
|
12486
|
-
if (!audioObj || typeof audioObj !== "object") continue;
|
|
12487
|
-
const audioData = audioObj.data;
|
|
12488
|
-
if (typeof audioData !== "string" || audioData.length === 0) continue;
|
|
12489
|
-
const decoded = Buffer.from(audioData, "base64");
|
|
12490
|
-
if (decoded.length === 0) {
|
|
12491
|
-
throw new Error("Invalid base64 audio data received from OpenRouter");
|
|
12492
|
-
}
|
|
12493
|
-
audioChunks.push(decoded);
|
|
12494
|
-
}
|
|
12495
|
-
const audioBuffer = Buffer.concat(audioChunks);
|
|
12496
|
-
if (audioBuffer.length === 0) {
|
|
12497
|
-
throw new Error(
|
|
12498
|
-
"OpenRouter TTS returned no audio data. The model may have failed to generate audio or the stream was interrupted."
|
|
14616
|
+
const tools = this.resolveTools(options, spec);
|
|
14617
|
+
return { ...options, background, tools };
|
|
14618
|
+
}
|
|
14619
|
+
resolveTools(options, spec) {
|
|
14620
|
+
if (options.tools === void 0) {
|
|
14621
|
+
return spec.requiredTools;
|
|
14622
|
+
}
|
|
14623
|
+
for (const tool of options.tools) {
|
|
14624
|
+
if (!spec.capabilities.tools.includes(tool.type)) {
|
|
14625
|
+
throw new ResearchValidationError(
|
|
14626
|
+
`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.")
|
|
12499
14627
|
);
|
|
12500
14628
|
}
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
14629
|
+
}
|
|
14630
|
+
const hasDataSource = options.tools.some(
|
|
14631
|
+
(tool) => RESEARCH_DATA_SOURCE_TOOL_TYPES.includes(tool.type)
|
|
14632
|
+
);
|
|
14633
|
+
if (!hasDataSource && spec.requiredTools) {
|
|
14634
|
+
return [...spec.requiredTools, ...options.tools];
|
|
14635
|
+
}
|
|
14636
|
+
return options.tools;
|
|
14637
|
+
}
|
|
14638
|
+
enforceLifecycle(spec) {
|
|
14639
|
+
const shutdownDate = spec.metadata?.shutdownDate;
|
|
14640
|
+
if (!shutdownDate) {
|
|
14641
|
+
return;
|
|
14642
|
+
}
|
|
14643
|
+
const shutdownMs = Date.parse(shutdownDate);
|
|
14644
|
+
if (Number.isNaN(shutdownMs)) {
|
|
14645
|
+
return;
|
|
14646
|
+
}
|
|
14647
|
+
const nowMs = this.now();
|
|
14648
|
+
if (nowMs >= shutdownMs) {
|
|
14649
|
+
throw new ResearchDeprecatedModelError({
|
|
14650
|
+
modelId: spec.modelId,
|
|
14651
|
+
shutdownDate,
|
|
14652
|
+
replacement: spec.metadata?.replacement
|
|
12525
14653
|
});
|
|
12526
|
-
|
|
12527
|
-
|
|
14654
|
+
}
|
|
14655
|
+
const warningStartMs = shutdownMs - RESEARCH_SHUTDOWN_WARNING_WINDOW_DAYS * MS_PER_DAY;
|
|
14656
|
+
if (nowMs >= warningStartMs) {
|
|
14657
|
+
this.logger.warn(
|
|
14658
|
+
`Research model "${spec.modelId}" shuts down on ${shutdownDate}` + (spec.metadata?.replacement ? ` \u2014 migrate to "${spec.metadata.replacement}".` : ".")
|
|
12528
14659
|
);
|
|
12529
14660
|
}
|
|
12530
14661
|
}
|
|
@@ -12532,36 +14663,6 @@ Original error: ${error.message}`
|
|
|
12532
14663
|
}
|
|
12533
14664
|
});
|
|
12534
14665
|
|
|
12535
|
-
// src/providers/discovery.ts
|
|
12536
|
-
function discoverProviderAdapters() {
|
|
12537
|
-
const adapters = [];
|
|
12538
|
-
for (const discover of DISCOVERERS) {
|
|
12539
|
-
const adapter = discover();
|
|
12540
|
-
if (adapter) {
|
|
12541
|
-
adapters.push(adapter);
|
|
12542
|
-
}
|
|
12543
|
-
}
|
|
12544
|
-
return adapters;
|
|
12545
|
-
}
|
|
12546
|
-
var DISCOVERERS;
|
|
12547
|
-
var init_discovery = __esm({
|
|
12548
|
-
"src/providers/discovery.ts"() {
|
|
12549
|
-
"use strict";
|
|
12550
|
-
init_anthropic();
|
|
12551
|
-
init_gemini();
|
|
12552
|
-
init_huggingface();
|
|
12553
|
-
init_openai();
|
|
12554
|
-
init_openrouter();
|
|
12555
|
-
DISCOVERERS = [
|
|
12556
|
-
createOpenAIProviderFromEnv,
|
|
12557
|
-
createAnthropicProviderFromEnv,
|
|
12558
|
-
createGeminiProviderFromEnv,
|
|
12559
|
-
createHuggingFaceProviderFromEnv,
|
|
12560
|
-
createOpenRouterProviderFromEnv
|
|
12561
|
-
];
|
|
12562
|
-
}
|
|
12563
|
-
});
|
|
12564
|
-
|
|
12565
14666
|
// src/core/model-registry.ts
|
|
12566
14667
|
var ModelRegistry;
|
|
12567
14668
|
var init_model_registry = __esm({
|
|
@@ -12786,6 +14887,8 @@ var init_image = __esm({
|
|
|
12786
14887
|
this.adapters = adapters;
|
|
12787
14888
|
this.defaultProvider = defaultProvider;
|
|
12788
14889
|
}
|
|
14890
|
+
adapters;
|
|
14891
|
+
defaultProvider;
|
|
12789
14892
|
/**
|
|
12790
14893
|
* Generate images from a text prompt.
|
|
12791
14894
|
*
|
|
@@ -12838,6 +14941,8 @@ var init_speech = __esm({
|
|
|
12838
14941
|
this.adapters = adapters;
|
|
12839
14942
|
this.defaultProvider = defaultProvider;
|
|
12840
14943
|
}
|
|
14944
|
+
adapters;
|
|
14945
|
+
defaultProvider;
|
|
12841
14946
|
/**
|
|
12842
14947
|
* Generate speech audio from text.
|
|
12843
14948
|
*
|
|
@@ -12933,6 +15038,7 @@ var init_text = __esm({
|
|
|
12933
15038
|
constructor(client) {
|
|
12934
15039
|
this.client = client;
|
|
12935
15040
|
}
|
|
15041
|
+
client;
|
|
12936
15042
|
/**
|
|
12937
15043
|
* Generate a complete text response.
|
|
12938
15044
|
*
|
|
@@ -12968,6 +15074,7 @@ var init_vision = __esm({
|
|
|
12968
15074
|
constructor(client) {
|
|
12969
15075
|
this.client = client;
|
|
12970
15076
|
}
|
|
15077
|
+
client;
|
|
12971
15078
|
/**
|
|
12972
15079
|
* Build a message builder with the image content attached.
|
|
12973
15080
|
* Handles URLs, data URLs, base64 strings, and binary buffers.
|
|
@@ -13094,6 +15201,7 @@ var init_options = __esm({
|
|
|
13094
15201
|
constructor(defaultProvider = "openai") {
|
|
13095
15202
|
this.defaultProvider = defaultProvider;
|
|
13096
15203
|
}
|
|
15204
|
+
defaultProvider;
|
|
13097
15205
|
parse(identifier) {
|
|
13098
15206
|
const trimmed = identifier.trim();
|
|
13099
15207
|
if (!trimmed) {
|
|
@@ -13125,6 +15233,7 @@ var init_client = __esm({
|
|
|
13125
15233
|
"use strict";
|
|
13126
15234
|
init_builder();
|
|
13127
15235
|
init_discovery();
|
|
15236
|
+
init_namespace();
|
|
13128
15237
|
init_constants();
|
|
13129
15238
|
init_model_registry();
|
|
13130
15239
|
init_image();
|
|
@@ -13143,6 +15252,12 @@ var init_client = __esm({
|
|
|
13143
15252
|
image;
|
|
13144
15253
|
speech;
|
|
13145
15254
|
vision;
|
|
15255
|
+
/**
|
|
15256
|
+
* Deep research — long-running, server-side research jobs with cited
|
|
15257
|
+
* reports (OpenAI Responses, Gemini Interactions, OpenRouter research
|
|
15258
|
+
* models). See docs/specs/002-deep-research.md.
|
|
15259
|
+
*/
|
|
15260
|
+
research;
|
|
13146
15261
|
constructor(...args) {
|
|
13147
15262
|
let adapters = [];
|
|
13148
15263
|
let defaultProvider;
|
|
@@ -13194,6 +15309,7 @@ var init_client = __esm({
|
|
|
13194
15309
|
this.image = new ImageNamespace(this.adapters, this.defaultProvider);
|
|
13195
15310
|
this.speech = new SpeechNamespace(this.adapters, this.defaultProvider);
|
|
13196
15311
|
this.vision = new VisionNamespace(this);
|
|
15312
|
+
this.research = new ResearchNamespace(this.adapters, this.parser);
|
|
13197
15313
|
}
|
|
13198
15314
|
stream(options) {
|
|
13199
15315
|
const descriptor = this.parser.parse(options.model);
|
|
@@ -13460,8 +15576,8 @@ var init_builder = __esm({
|
|
|
13460
15576
|
return this;
|
|
13461
15577
|
}
|
|
13462
15578
|
/** Set maximum iterations. */
|
|
13463
|
-
withMaxIterations(
|
|
13464
|
-
this.core.maxIterations =
|
|
15579
|
+
withMaxIterations(max2) {
|
|
15580
|
+
this.core.maxIterations = max2;
|
|
13465
15581
|
return this;
|
|
13466
15582
|
}
|
|
13467
15583
|
/** Set the budget limit in USD. */
|
|
@@ -13590,10 +15706,10 @@ var init_builder = __esm({
|
|
|
13590
15706
|
return this;
|
|
13591
15707
|
}
|
|
13592
15708
|
/** Set the maximum number of gadgets to execute per LLM response. */
|
|
13593
|
-
withMaxGadgetsPerResponse(
|
|
13594
|
-
if (
|
|
13595
|
-
if (!Number.isInteger(
|
|
13596
|
-
this.gadgets.maxGadgetsPerResponse =
|
|
15709
|
+
withMaxGadgetsPerResponse(max2) {
|
|
15710
|
+
if (max2 < 0) throw new Error("maxGadgetsPerResponse must be a non-negative number");
|
|
15711
|
+
if (!Number.isInteger(max2)) throw new Error("maxGadgetsPerResponse must be an integer");
|
|
15712
|
+
this.gadgets.maxGadgetsPerResponse = max2;
|
|
13597
15713
|
return this;
|
|
13598
15714
|
}
|
|
13599
15715
|
/** Enable or disable gadget output limiting. */
|
|
@@ -14294,6 +16410,8 @@ var init_cost_reporting_client = __esm({
|
|
|
14294
16410
|
}
|
|
14295
16411
|
};
|
|
14296
16412
|
}
|
|
16413
|
+
client;
|
|
16414
|
+
reportCost;
|
|
14297
16415
|
image;
|
|
14298
16416
|
speech;
|
|
14299
16417
|
/**
|
|
@@ -14570,13 +16688,22 @@ var init_parser2 = __esm({
|
|
|
14570
16688
|
GadgetCallParser = class {
|
|
14571
16689
|
buffer = "";
|
|
14572
16690
|
lastEmittedTextOffset = 0;
|
|
16691
|
+
/** Non-null only while a single trailing gadget block is mid-stream. */
|
|
16692
|
+
currentPartial = null;
|
|
14573
16693
|
startPrefix;
|
|
14574
16694
|
endPrefix;
|
|
14575
16695
|
argPrefix;
|
|
16696
|
+
/** Length of the longest marker; `maxMarkerLength - 1` is the scan-resume overlap. */
|
|
16697
|
+
maxMarkerLength;
|
|
14576
16698
|
constructor(options = {}) {
|
|
14577
16699
|
this.startPrefix = options.startPrefix ?? GADGET_START_PREFIX;
|
|
14578
16700
|
this.endPrefix = options.endPrefix ?? GADGET_END_PREFIX;
|
|
14579
16701
|
this.argPrefix = options.argPrefix ?? GADGET_ARG_PREFIX;
|
|
16702
|
+
this.maxMarkerLength = Math.max(
|
|
16703
|
+
this.startPrefix.length,
|
|
16704
|
+
this.endPrefix.length,
|
|
16705
|
+
this.argPrefix.length
|
|
16706
|
+
);
|
|
14580
16707
|
}
|
|
14581
16708
|
/**
|
|
14582
16709
|
* Extract and consume text up to the given index.
|
|
@@ -14663,12 +16790,13 @@ var init_parser2 = __esm({
|
|
|
14663
16790
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
14664
16791
|
if (metadataEndIndex === -1) break;
|
|
14665
16792
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
14666
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
16793
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
14667
16794
|
const contentStartIndex = metadataEndIndex + 1;
|
|
14668
16795
|
let partEndIndex;
|
|
14669
16796
|
let endMarkerLength = 0;
|
|
14670
|
-
const
|
|
14671
|
-
const
|
|
16797
|
+
const bodySearchStart = this.currentPartial ? contentStartIndex + Math.max(0, this.currentPartial.bodyScannedLen - (this.maxMarkerLength - 1)) : contentStartIndex;
|
|
16798
|
+
const nextStartPos = this.buffer.indexOf(this.startPrefix, bodySearchStart);
|
|
16799
|
+
const endPos = this.buffer.indexOf(this.endPrefix, bodySearchStart);
|
|
14672
16800
|
if (nextStartPos !== -1 && (endPos === -1 || nextStartPos < endPos)) {
|
|
14673
16801
|
partEndIndex = nextStartPos;
|
|
14674
16802
|
endMarkerLength = 0;
|
|
@@ -14676,9 +16804,22 @@ var init_parser2 = __esm({
|
|
|
14676
16804
|
partEndIndex = endPos;
|
|
14677
16805
|
endMarkerLength = this.endPrefix.length;
|
|
14678
16806
|
} else {
|
|
16807
|
+
if (!this.currentPartial) {
|
|
16808
|
+
this.currentPartial = this.newPartialState(gadgetName, invocationId, dependencies);
|
|
16809
|
+
}
|
|
16810
|
+
yield* this.emitArgPartials(
|
|
16811
|
+
this.currentPartial,
|
|
16812
|
+
contentStartIndex,
|
|
16813
|
+
this.buffer.length,
|
|
16814
|
+
false
|
|
16815
|
+
);
|
|
14679
16816
|
break;
|
|
14680
16817
|
}
|
|
14681
|
-
const
|
|
16818
|
+
const rawSlice = this.buffer.substring(contentStartIndex, partEndIndex);
|
|
16819
|
+
if (this.currentPartial) {
|
|
16820
|
+
yield* this.emitArgPartials(this.currentPartial, contentStartIndex, partEndIndex, true);
|
|
16821
|
+
}
|
|
16822
|
+
const parametersRaw = rawSlice.trim();
|
|
14682
16823
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
14683
16824
|
yield {
|
|
14684
16825
|
type: "gadget_call",
|
|
@@ -14691,6 +16832,7 @@ var init_parser2 = __esm({
|
|
|
14691
16832
|
dependencies
|
|
14692
16833
|
}
|
|
14693
16834
|
};
|
|
16835
|
+
this.currentPartial = null;
|
|
14694
16836
|
startIndex = partEndIndex + endMarkerLength;
|
|
14695
16837
|
this.lastEmittedTextOffset = startIndex;
|
|
14696
16838
|
}
|
|
@@ -14699,6 +16841,117 @@ var init_parser2 = __esm({
|
|
|
14699
16841
|
this.lastEmittedTextOffset = 0;
|
|
14700
16842
|
}
|
|
14701
16843
|
}
|
|
16844
|
+
/** Create fresh partial-tracking state for a newly-started streaming gadget. */
|
|
16845
|
+
newPartialState(gadgetName, invocationId, dependencies) {
|
|
16846
|
+
return {
|
|
16847
|
+
gadgetName,
|
|
16848
|
+
invocationId,
|
|
16849
|
+
dependencies,
|
|
16850
|
+
emittedFieldLengths: /* @__PURE__ */ new Map(),
|
|
16851
|
+
completedFields: /* @__PURE__ */ new Set(),
|
|
16852
|
+
bodyScannedLen: 0,
|
|
16853
|
+
lastArgRelOffset: -1
|
|
16854
|
+
};
|
|
16855
|
+
}
|
|
16856
|
+
/**
|
|
16857
|
+
* Emit per-field "growing value" partials for an in-progress (or, when
|
|
16858
|
+
* `allComplete`, a just-completed) gadget body delimited by [bodyStart, bodyEnd).
|
|
16859
|
+
*
|
|
16860
|
+
* Incremental by design: each call resumes the `!!!ARG:` scan near where the last
|
|
16861
|
+
* one stopped (backing off by one marker's worth so a marker split across a chunk
|
|
16862
|
+
* boundary is still found) and only re-touches the in-progress field, so a long
|
|
16863
|
+
* streamed body costs O(new bytes) per feed instead of O(body). Every field except
|
|
16864
|
+
* the in-progress (last) one is complete — a following `!!!ARG:` terminated it; the
|
|
16865
|
+
* last field is tentative unless `allComplete`. The tentative field holds back any
|
|
16866
|
+
* suffix that is a partial prefix of a gadget marker so it never leaks into a value.
|
|
16867
|
+
*
|
|
16868
|
+
* We deliberately do NOT run stripMarkdownFences here: an unbalanced opening fence
|
|
16869
|
+
* sits before the first `!!!ARG:` (never emitted) and the authoritative gadget_call
|
|
16870
|
+
* still strips fences from the full raw parameters.
|
|
16871
|
+
*/
|
|
16872
|
+
*emitArgPartials(state, bodyStart, bodyEnd, allComplete) {
|
|
16873
|
+
const argLen = this.argPrefix.length;
|
|
16874
|
+
let searchAbs = bodyStart + Math.max(0, state.bodyScannedLen - (this.maxMarkerLength - 1));
|
|
16875
|
+
if (state.lastArgRelOffset >= 0) {
|
|
16876
|
+
searchAbs = Math.max(searchAbs, bodyStart + state.lastArgRelOffset + argLen);
|
|
16877
|
+
}
|
|
16878
|
+
while (true) {
|
|
16879
|
+
const argAbs = this.buffer.indexOf(this.argPrefix, searchAbs);
|
|
16880
|
+
if (argAbs === -1 || argAbs >= bodyEnd) break;
|
|
16881
|
+
if (state.lastArgRelOffset >= 0) {
|
|
16882
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, argAbs, true);
|
|
16883
|
+
}
|
|
16884
|
+
state.lastArgRelOffset = argAbs - bodyStart;
|
|
16885
|
+
searchAbs = argAbs + argLen;
|
|
16886
|
+
}
|
|
16887
|
+
if (state.lastArgRelOffset >= 0) {
|
|
16888
|
+
yield* this.emitFieldRange(state, bodyStart + state.lastArgRelOffset, bodyEnd, allComplete);
|
|
16889
|
+
}
|
|
16890
|
+
state.bodyScannedLen = bodyEnd - bodyStart;
|
|
16891
|
+
}
|
|
16892
|
+
/**
|
|
16893
|
+
* Emit a single field whose `!!!ARG:` marker starts at `markerAbs` and whose value
|
|
16894
|
+
* runs to `valueEndAbs` (the next marker, or the body end). Mirrors the per-field
|
|
16895
|
+
* semantics of the old split-based emitter: field-path line, hold-back for a
|
|
16896
|
+
* tentative value, single trailing-newline strip.
|
|
16897
|
+
*/
|
|
16898
|
+
*emitFieldRange(state, markerAbs, valueEndAbs, complete2) {
|
|
16899
|
+
const pathStart = markerAbs + this.argPrefix.length;
|
|
16900
|
+
const newlineAbs = this.buffer.indexOf("\n", pathStart);
|
|
16901
|
+
if (newlineAbs === -1 || newlineAbs >= valueEndAbs) {
|
|
16902
|
+
if (!complete2) return;
|
|
16903
|
+
const pointer = this.buffer.substring(pathStart, valueEndAbs).trim();
|
|
16904
|
+
if (pointer) yield* this.emitFieldDelta(state, pointer, "", true);
|
|
16905
|
+
return;
|
|
16906
|
+
}
|
|
16907
|
+
const fieldPath = this.buffer.substring(pathStart, newlineAbs).trim();
|
|
16908
|
+
if (!fieldPath) return;
|
|
16909
|
+
let value = this.buffer.substring(newlineAbs + 1, valueEndAbs);
|
|
16910
|
+
if (!complete2) {
|
|
16911
|
+
const hold = this.trailingPartialMarkerLength(value);
|
|
16912
|
+
if (hold > 0) value = value.slice(0, value.length - hold);
|
|
16913
|
+
}
|
|
16914
|
+
if (value.endsWith("\n")) value = value.slice(0, -1);
|
|
16915
|
+
yield* this.emitFieldDelta(state, fieldPath, value, complete2);
|
|
16916
|
+
}
|
|
16917
|
+
/**
|
|
16918
|
+
* Emit a single partial for a field, but only when its value grew or it newly
|
|
16919
|
+
* completed — keeping event volume proportional to field growth, not characters.
|
|
16920
|
+
*/
|
|
16921
|
+
*emitFieldDelta(state, fieldPath, value, fieldComplete) {
|
|
16922
|
+
const previousLength = state.emittedFieldLengths.get(fieldPath) ?? -1;
|
|
16923
|
+
const grew = value.length > previousLength;
|
|
16924
|
+
const newlyComplete = fieldComplete && !state.completedFields.has(fieldPath);
|
|
16925
|
+
if (!grew && !newlyComplete) return;
|
|
16926
|
+
const delta = previousLength < 0 ? value : value.slice(Math.min(previousLength, value.length));
|
|
16927
|
+
state.emittedFieldLengths.set(fieldPath, value.length);
|
|
16928
|
+
if (fieldComplete) state.completedFields.add(fieldPath);
|
|
16929
|
+
yield {
|
|
16930
|
+
type: "gadget_args_partial",
|
|
16931
|
+
invocationId: state.invocationId,
|
|
16932
|
+
gadgetName: state.gadgetName,
|
|
16933
|
+
fieldPath,
|
|
16934
|
+
value,
|
|
16935
|
+
delta,
|
|
16936
|
+
isFieldComplete: fieldComplete
|
|
16937
|
+
};
|
|
16938
|
+
}
|
|
16939
|
+
/**
|
|
16940
|
+
* Length of the longest suffix of `value` that is a proper prefix of any gadget
|
|
16941
|
+
* marker (start/end/arg). Used to hold back the beginning of an incoming marker
|
|
16942
|
+
* so it never appears inside a streamed value.
|
|
16943
|
+
*/
|
|
16944
|
+
trailingPartialMarkerLength(value) {
|
|
16945
|
+
const markers = [this.startPrefix, this.endPrefix, this.argPrefix];
|
|
16946
|
+
const limit = Math.min(this.maxMarkerLength - 1, value.length);
|
|
16947
|
+
for (let len = limit; len >= 1; len--) {
|
|
16948
|
+
const suffix = value.slice(value.length - len);
|
|
16949
|
+
for (const marker of markers) {
|
|
16950
|
+
if (suffix.length < marker.length && marker.startsWith(suffix)) return len;
|
|
16951
|
+
}
|
|
16952
|
+
}
|
|
16953
|
+
return 0;
|
|
16954
|
+
}
|
|
14702
16955
|
// Finalize parsing and return remaining text or incomplete gadgets
|
|
14703
16956
|
*finalize() {
|
|
14704
16957
|
const startIndex = this.buffer.indexOf(this.startPrefix, this.lastEmittedTextOffset);
|
|
@@ -14711,9 +16964,18 @@ var init_parser2 = __esm({
|
|
|
14711
16964
|
const metadataEndIndex = this.buffer.indexOf("\n", metadataStartIndex);
|
|
14712
16965
|
if (metadataEndIndex !== -1) {
|
|
14713
16966
|
const headerLine = this.buffer.substring(metadataStartIndex, metadataEndIndex).trim();
|
|
14714
|
-
const { gadgetName, invocationId, dependencies } = this.parseInvocationMetadata(headerLine);
|
|
16967
|
+
const { gadgetName, invocationId, dependencies } = this.currentPartial ? this.currentPartial : this.parseInvocationMetadata(headerLine);
|
|
14715
16968
|
const contentStartIndex = metadataEndIndex + 1;
|
|
14716
|
-
const
|
|
16969
|
+
const contentRaw = this.buffer.substring(contentStartIndex);
|
|
16970
|
+
if (this.currentPartial) {
|
|
16971
|
+
yield* this.emitArgPartials(
|
|
16972
|
+
this.currentPartial,
|
|
16973
|
+
contentStartIndex,
|
|
16974
|
+
this.buffer.length,
|
|
16975
|
+
true
|
|
16976
|
+
);
|
|
16977
|
+
}
|
|
16978
|
+
const parametersRaw = contentRaw.trim();
|
|
14717
16979
|
const { parameters, parseError } = this.parseParameters(parametersRaw);
|
|
14718
16980
|
yield {
|
|
14719
16981
|
type: "gadget_call",
|
|
@@ -14726,6 +16988,7 @@ var init_parser2 = __esm({
|
|
|
14726
16988
|
dependencies
|
|
14727
16989
|
}
|
|
14728
16990
|
};
|
|
16991
|
+
this.currentPartial = null;
|
|
14729
16992
|
return;
|
|
14730
16993
|
}
|
|
14731
16994
|
}
|
|
@@ -14738,6 +17001,7 @@ var init_parser2 = __esm({
|
|
|
14738
17001
|
reset() {
|
|
14739
17002
|
this.buffer = "";
|
|
14740
17003
|
this.lastEmittedTextOffset = 0;
|
|
17004
|
+
this.currentPartial = null;
|
|
14741
17005
|
}
|
|
14742
17006
|
};
|
|
14743
17007
|
}
|
|
@@ -15663,6 +17927,29 @@ async function notifyGadgetStart(ctx) {
|
|
|
15663
17927
|
await safeObserve(() => hookFn(context), ctx.logger);
|
|
15664
17928
|
}
|
|
15665
17929
|
}
|
|
17930
|
+
async function notifyGadgetArgsPartial(ctx) {
|
|
17931
|
+
if (!ctx.hooks?.onGadgetArgsPartial && !ctx.parentObservers?.onGadgetArgsPartial) return;
|
|
17932
|
+
const subagentContext = ctx.tree && ctx.parentNodeId ? getSubagentContextForNode(ctx.tree, ctx.parentNodeId) : void 0;
|
|
17933
|
+
const context = {
|
|
17934
|
+
iteration: ctx.iteration,
|
|
17935
|
+
invocationId: ctx.event.invocationId,
|
|
17936
|
+
gadgetName: ctx.event.gadgetName,
|
|
17937
|
+
fieldPath: ctx.event.fieldPath,
|
|
17938
|
+
value: ctx.event.value,
|
|
17939
|
+
delta: ctx.event.delta,
|
|
17940
|
+
isFieldComplete: ctx.event.isFieldComplete,
|
|
17941
|
+
logger: ctx.logger,
|
|
17942
|
+
subagentContext
|
|
17943
|
+
};
|
|
17944
|
+
if (ctx.hooks?.onGadgetArgsPartial) {
|
|
17945
|
+
const hookFn = ctx.hooks.onGadgetArgsPartial;
|
|
17946
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
17947
|
+
}
|
|
17948
|
+
if (ctx.parentObservers?.onGadgetArgsPartial) {
|
|
17949
|
+
const hookFn = ctx.parentObservers.onGadgetArgsPartial;
|
|
17950
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
17951
|
+
}
|
|
17952
|
+
}
|
|
15666
17953
|
async function notifyGadgetComplete(ctx) {
|
|
15667
17954
|
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
15668
17955
|
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
@@ -16471,6 +18758,7 @@ var init_stream_processor = __esm({
|
|
|
16471
18758
|
init_gadget_dispatcher();
|
|
16472
18759
|
init_gadget_hook_lifecycle();
|
|
16473
18760
|
init_gadget_limit_guard();
|
|
18761
|
+
init_observer_notifier();
|
|
16474
18762
|
init_safe_observe();
|
|
16475
18763
|
StreamProcessor = class {
|
|
16476
18764
|
iteration;
|
|
@@ -16479,6 +18767,10 @@ var init_stream_processor = __esm({
|
|
|
16479
18767
|
parser;
|
|
16480
18768
|
// Execution Tree context
|
|
16481
18769
|
tree;
|
|
18770
|
+
/** LLM-call node these gadgets hang off; used to derive subagentContext for partials. */
|
|
18771
|
+
parentNodeId;
|
|
18772
|
+
/** Parent agent observers (subagent visibility) — also notified for arg partials. */
|
|
18773
|
+
parentObservers;
|
|
16482
18774
|
responseText = "";
|
|
16483
18775
|
// Dependency resolution is delegated to GadgetDependencyResolver
|
|
16484
18776
|
dependencyResolver;
|
|
@@ -16492,6 +18784,8 @@ var init_stream_processor = __esm({
|
|
|
16492
18784
|
this.hooks = options.hooks ?? {};
|
|
16493
18785
|
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
16494
18786
|
this.tree = options.tree;
|
|
18787
|
+
this.parentNodeId = options.parentNodeId;
|
|
18788
|
+
this.parentObservers = options.parentObservers;
|
|
16495
18789
|
this.dependencyResolver = new GadgetDependencyResolver({
|
|
16496
18790
|
priorCompletedInvocations: options.priorCompletedInvocations,
|
|
16497
18791
|
priorFailedInvocations: options.priorFailedInvocations
|
|
@@ -16693,6 +18987,17 @@ var init_stream_processor = __esm({
|
|
|
16693
18987
|
for await (const e of this.dispatcher.dispatch(event.call)) {
|
|
16694
18988
|
yield e;
|
|
16695
18989
|
}
|
|
18990
|
+
} else if (event.type === "gadget_args_partial") {
|
|
18991
|
+
await notifyGadgetArgsPartial({
|
|
18992
|
+
tree: this.tree,
|
|
18993
|
+
hooks: this.hooks.observers,
|
|
18994
|
+
parentObservers: this.parentObservers,
|
|
18995
|
+
logger: this.logger,
|
|
18996
|
+
iteration: this.iteration,
|
|
18997
|
+
parentNodeId: this.parentNodeId,
|
|
18998
|
+
event
|
|
18999
|
+
});
|
|
19000
|
+
yield event;
|
|
16696
19001
|
} else {
|
|
16697
19002
|
yield event;
|
|
16698
19003
|
}
|
|
@@ -16864,7 +19169,7 @@ var init_stream_processor_factory = __esm({
|
|
|
16864
19169
|
|
|
16865
19170
|
// src/mcp/errors.ts
|
|
16866
19171
|
var McpError, McpUntrustedCommandError, McpConnectError, McpToolCallError, McpTimeoutError, JsonSchemaConversionError;
|
|
16867
|
-
var
|
|
19172
|
+
var init_errors3 = __esm({
|
|
16868
19173
|
"src/mcp/errors.ts"() {
|
|
16869
19174
|
"use strict";
|
|
16870
19175
|
McpError = class extends Error {
|
|
@@ -16950,7 +19255,7 @@ var init_allowlist = __esm({
|
|
|
16950
19255
|
"src/mcp/allowlist.ts"() {
|
|
16951
19256
|
"use strict";
|
|
16952
19257
|
import_node_path6 = __toESM(require("path"), 1);
|
|
16953
|
-
|
|
19258
|
+
init_errors3();
|
|
16954
19259
|
DEFAULT_MCP_COMMAND_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
16955
19260
|
"npx",
|
|
16956
19261
|
"node",
|
|
@@ -16988,7 +19293,7 @@ var init_client2 = __esm({
|
|
|
16988
19293
|
"src/mcp/client.ts"() {
|
|
16989
19294
|
"use strict";
|
|
16990
19295
|
init_allowlist();
|
|
16991
|
-
|
|
19296
|
+
init_errors3();
|
|
16992
19297
|
cachedSdk = null;
|
|
16993
19298
|
DEFAULT_CLIENT_INFO = { name: "llmist", version: "0.0.0" };
|
|
16994
19299
|
McpClient = class {
|
|
@@ -16997,6 +19302,7 @@ var init_client2 = __esm({
|
|
|
16997
19302
|
this.injectedTransport = opts?.transport;
|
|
16998
19303
|
this.clientInfo = opts?.clientInfo ?? DEFAULT_CLIENT_INFO;
|
|
16999
19304
|
}
|
|
19305
|
+
spec;
|
|
17000
19306
|
sdkClient = null;
|
|
17001
19307
|
spawnedPid = null;
|
|
17002
19308
|
closed = false;
|
|
@@ -17570,7 +19876,7 @@ var init_json_schema_to_zod = __esm({
|
|
|
17570
19876
|
"src/mcp/json-schema-to-zod.ts"() {
|
|
17571
19877
|
"use strict";
|
|
17572
19878
|
import_zod4 = require("zod");
|
|
17573
|
-
|
|
19879
|
+
init_errors3();
|
|
17574
19880
|
}
|
|
17575
19881
|
});
|
|
17576
19882
|
|
|
@@ -18610,7 +20916,17 @@ __export(index_exports, {
|
|
|
18610
20916
|
OpenAIChatProvider: () => OpenAIChatProvider,
|
|
18611
20917
|
OpenAICompatibleProvider: () => OpenAICompatibleProvider,
|
|
18612
20918
|
OpenRouterProvider: () => OpenRouterProvider,
|
|
20919
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES: () => RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
18613
20920
|
RateLimitTracker: () => RateLimitTracker,
|
|
20921
|
+
ResearchDeprecatedModelError: () => ResearchDeprecatedModelError,
|
|
20922
|
+
ResearchJobNotResumableError: () => ResearchJobNotResumableError,
|
|
20923
|
+
ResearchNamespace: () => ResearchNamespace,
|
|
20924
|
+
ResearchNotPollableError: () => ResearchNotPollableError,
|
|
20925
|
+
ResearchNotSupportedError: () => ResearchNotSupportedError,
|
|
20926
|
+
ResearchResultCollector: () => ResearchResultCollector,
|
|
20927
|
+
ResearchStreamConsumedError: () => ResearchStreamConsumedError,
|
|
20928
|
+
ResearchTimeoutError: () => ResearchTimeoutError,
|
|
20929
|
+
ResearchValidationError: () => ResearchValidationError,
|
|
18614
20930
|
SimpleSessionManager: () => SimpleSessionManager,
|
|
18615
20931
|
Skill: () => Skill,
|
|
18616
20932
|
SkillRegistry: () => SkillRegistry,
|
|
@@ -18644,6 +20960,7 @@ __export(index_exports, {
|
|
|
18644
20960
|
detectImageMimeType: () => detectImageMimeType,
|
|
18645
20961
|
discoverProviderAdapters: () => discoverProviderAdapters,
|
|
18646
20962
|
discoverSkills: () => discoverSkills,
|
|
20963
|
+
estimateResearchCost: () => estimateResearchCost,
|
|
18647
20964
|
extractMessageText: () => extractMessageText,
|
|
18648
20965
|
extractRetryAfterMs: () => extractRetryAfterMs,
|
|
18649
20966
|
filterByDepth: () => filterByDepth,
|
|
@@ -18923,7 +21240,7 @@ init_typed_gadget();
|
|
|
18923
21240
|
// src/mcp/index.ts
|
|
18924
21241
|
init_allowlist();
|
|
18925
21242
|
init_client2();
|
|
18926
|
-
|
|
21243
|
+
init_errors3();
|
|
18927
21244
|
|
|
18928
21245
|
// src/mcp/gadget-exporter.ts
|
|
18929
21246
|
init_schema_to_json();
|
|
@@ -19164,6 +21481,14 @@ init_tool_adapter();
|
|
|
19164
21481
|
// src/index.ts
|
|
19165
21482
|
init_constants2();
|
|
19166
21483
|
|
|
21484
|
+
// src/research/index.ts
|
|
21485
|
+
init_collector();
|
|
21486
|
+
init_constants3();
|
|
21487
|
+
init_cost();
|
|
21488
|
+
init_errors2();
|
|
21489
|
+
init_namespace();
|
|
21490
|
+
init_types();
|
|
21491
|
+
|
|
19167
21492
|
// src/utils/config-resolver.ts
|
|
19168
21493
|
function resolveValue(ctx, gadgetName, options) {
|
|
19169
21494
|
const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
|
|
@@ -19462,11 +21787,11 @@ var format = {
|
|
|
19462
21787
|
};
|
|
19463
21788
|
|
|
19464
21789
|
// src/utils/timing.ts
|
|
19465
|
-
function randomDelay(min,
|
|
19466
|
-
return Math.floor(Math.random() * (
|
|
21790
|
+
function randomDelay(min, max2) {
|
|
21791
|
+
return Math.floor(Math.random() * (max2 - min + 1)) + min;
|
|
19467
21792
|
}
|
|
19468
|
-
async function humanDelay(min = 50,
|
|
19469
|
-
const delay = randomDelay(min,
|
|
21793
|
+
async function humanDelay(min = 50, max2 = 150) {
|
|
21794
|
+
const delay = randomDelay(min, max2);
|
|
19470
21795
|
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
19471
21796
|
}
|
|
19472
21797
|
async function withTimeout(fn, timeoutMs, signal) {
|
|
@@ -19606,7 +21931,17 @@ function getHostExports2(ctx) {
|
|
|
19606
21931
|
OpenAIChatProvider,
|
|
19607
21932
|
OpenAICompatibleProvider,
|
|
19608
21933
|
OpenRouterProvider,
|
|
21934
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
19609
21935
|
RateLimitTracker,
|
|
21936
|
+
ResearchDeprecatedModelError,
|
|
21937
|
+
ResearchJobNotResumableError,
|
|
21938
|
+
ResearchNamespace,
|
|
21939
|
+
ResearchNotPollableError,
|
|
21940
|
+
ResearchNotSupportedError,
|
|
21941
|
+
ResearchResultCollector,
|
|
21942
|
+
ResearchStreamConsumedError,
|
|
21943
|
+
ResearchTimeoutError,
|
|
21944
|
+
ResearchValidationError,
|
|
19610
21945
|
SimpleSessionManager,
|
|
19611
21946
|
Skill,
|
|
19612
21947
|
SkillRegistry,
|
|
@@ -19640,6 +21975,7 @@ function getHostExports2(ctx) {
|
|
|
19640
21975
|
detectImageMimeType,
|
|
19641
21976
|
discoverProviderAdapters,
|
|
19642
21977
|
discoverSkills,
|
|
21978
|
+
estimateResearchCost,
|
|
19643
21979
|
extractMessageText,
|
|
19644
21980
|
extractRetryAfterMs,
|
|
19645
21981
|
filterByDepth,
|