llmist 18.3.0 → 18.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-DNB4DPM4.js → chunk-I4KQ7U7F.js} +23 -4
- package/dist/chunk-I4KQ7U7F.js.map +1 -0
- package/dist/index.cjs +2201 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +592 -1
- package/dist/index.d.ts +592 -1
- package/dist/index.js +2145 -38
- package/dist/index.js.map +1 -1
- package/dist/{runtime-KB7US2FQ.js → runtime-IUKW77QX.js} +2 -2
- package/package.json +2 -2
- package/dist/chunk-DNB4DPM4.js.map +0 -1
- /package/dist/{runtime-KB7US2FQ.js.map → runtime-IUKW77QX.js.map} +0 -0
package/dist/index.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;
|
|
@@ -5051,7 +5071,7 @@ var init_gadget_output_store = __esm({
|
|
|
5051
5071
|
charCount: content.length,
|
|
5052
5072
|
byteSize: encoder.encode(content).length,
|
|
5053
5073
|
lineCount: lines.length,
|
|
5054
|
-
maxLineLength: lines.reduce((
|
|
5074
|
+
maxLineLength: lines.reduce((max2, line) => Math.max(max2, line.length), 0),
|
|
5055
5075
|
timestamp: /* @__PURE__ */ new Date()
|
|
5056
5076
|
};
|
|
5057
5077
|
this.outputs.set(id, stored);
|
|
@@ -7702,6 +7722,7 @@ var init_base_provider = __esm({
|
|
|
7702
7722
|
constructor(client) {
|
|
7703
7723
|
this.client = client;
|
|
7704
7724
|
}
|
|
7725
|
+
client;
|
|
7705
7726
|
/**
|
|
7706
7727
|
* Template method that defines the skeleton of the streaming algorithm.
|
|
7707
7728
|
* This orchestrates the four-step process without dictating provider-specific details.
|
|
@@ -8071,6 +8092,7 @@ var init_gemini_cache_manager = __esm({
|
|
|
8071
8092
|
constructor(client) {
|
|
8072
8093
|
this.client = client;
|
|
8073
8094
|
}
|
|
8095
|
+
client;
|
|
8074
8096
|
activeCache = null;
|
|
8075
8097
|
/**
|
|
8076
8098
|
* Get or create a cache for the given content.
|
|
@@ -8613,6 +8635,621 @@ var init_gemini_models = __esm({
|
|
|
8613
8635
|
}
|
|
8614
8636
|
});
|
|
8615
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
|
+
|
|
8616
9253
|
// src/providers/gemini-speech-models.ts
|
|
8617
9254
|
function getGeminiSpeechModelSpec(modelId) {
|
|
8618
9255
|
return geminiSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -8810,6 +9447,8 @@ var init_gemini = __esm({
|
|
|
8810
9447
|
init_gemini_cache_manager();
|
|
8811
9448
|
init_gemini_image_models();
|
|
8812
9449
|
init_gemini_models();
|
|
9450
|
+
init_gemini_research();
|
|
9451
|
+
init_gemini_research_models();
|
|
8813
9452
|
init_gemini_speech_models();
|
|
8814
9453
|
init_utils();
|
|
8815
9454
|
GEMINI3_PRO_THINKING_LEVEL = {
|
|
@@ -9029,6 +9668,27 @@ var init_gemini = __esm({
|
|
|
9029
9668
|
format: spec?.defaultFormat ?? "wav"
|
|
9030
9669
|
};
|
|
9031
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
|
+
}
|
|
9032
9692
|
buildApiRequest(options, descriptor, _spec, messages) {
|
|
9033
9693
|
const contents = this.convertMessagesToContents(messages);
|
|
9034
9694
|
return this.buildApiRequestFromContents(options, descriptor, _spec, contents, null, 0);
|
|
@@ -10255,17 +10915,17 @@ var init_openai_compatible_provider = __esm({
|
|
|
10255
10915
|
async executeStreamRequest(payload, signal) {
|
|
10256
10916
|
const client = this.client;
|
|
10257
10917
|
const headers = this.getCustomHeaders();
|
|
10258
|
-
const
|
|
10918
|
+
const requestOptions3 = {};
|
|
10259
10919
|
if (signal) {
|
|
10260
|
-
|
|
10920
|
+
requestOptions3.signal = signal;
|
|
10261
10921
|
}
|
|
10262
10922
|
if (Object.keys(headers).length > 0) {
|
|
10263
|
-
|
|
10923
|
+
requestOptions3.headers = headers;
|
|
10264
10924
|
}
|
|
10265
10925
|
try {
|
|
10266
10926
|
const stream2 = await client.chat.completions.create(
|
|
10267
10927
|
payload,
|
|
10268
|
-
Object.keys(
|
|
10928
|
+
Object.keys(requestOptions3).length > 0 ? requestOptions3 : void 0
|
|
10269
10929
|
);
|
|
10270
10930
|
return stream2;
|
|
10271
10931
|
} catch (error) {
|
|
@@ -11142,53 +11802,596 @@ var init_openai_models = __esm({
|
|
|
11142
11802
|
}
|
|
11143
11803
|
});
|
|
11144
11804
|
|
|
11145
|
-
// src/providers/openai-
|
|
11146
|
-
function
|
|
11147
|
-
|
|
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;
|
|
11148
11835
|
}
|
|
11149
|
-
function
|
|
11150
|
-
|
|
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;
|
|
11151
11865
|
}
|
|
11152
|
-
function
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
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";
|
|
11157
11876
|
}
|
|
11158
|
-
|
|
11159
|
-
|
|
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
|
+
}
|
|
11160
11891
|
}
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
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;
|
|
11164
11909
|
}
|
|
11165
|
-
return
|
|
11910
|
+
return searches;
|
|
11166
11911
|
}
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
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],
|
|
11192
12395
|
formats: OPENAI_TTS_FORMATS,
|
|
11193
12396
|
maxInputLength: 4096,
|
|
11194
12397
|
defaultVoice: "alloy",
|
|
@@ -11297,6 +12500,8 @@ var init_openai = __esm({
|
|
|
11297
12500
|
init_constants2();
|
|
11298
12501
|
init_openai_image_models();
|
|
11299
12502
|
init_openai_models();
|
|
12503
|
+
init_openai_research();
|
|
12504
|
+
init_openai_research_models();
|
|
11300
12505
|
init_openai_speech_models();
|
|
11301
12506
|
init_utils();
|
|
11302
12507
|
ROLE_MAP2 = {
|
|
@@ -11400,6 +12605,32 @@ var init_openai = __esm({
|
|
|
11400
12605
|
format: format2
|
|
11401
12606
|
};
|
|
11402
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
|
+
}
|
|
11403
12634
|
buildApiRequest(options, descriptor, spec, messages) {
|
|
11404
12635
|
const { maxTokens, temperature, topP, stopSequences, extra, reasoning } = options;
|
|
11405
12636
|
const supportsTemperature = spec?.metadata?.supportsTemperature !== false;
|
|
@@ -12127,6 +13358,217 @@ var init_openrouter_models = __esm({
|
|
|
12127
13358
|
}
|
|
12128
13359
|
});
|
|
12129
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
|
+
|
|
12130
13572
|
// src/providers/openrouter-speech-models.ts
|
|
12131
13573
|
function getOpenRouterSpeechModelSpec(modelId) {
|
|
12132
13574
|
return openrouterSpeechModels.find((m) => m.modelId === modelId);
|
|
@@ -12254,8 +13696,11 @@ var init_openrouter = __esm({
|
|
|
12254
13696
|
import_openai4 = __toESM(require("openai"), 1);
|
|
12255
13697
|
init_model_shortcuts();
|
|
12256
13698
|
init_logger();
|
|
13699
|
+
init_constants3();
|
|
12257
13700
|
init_openai_compatible_provider();
|
|
12258
13701
|
init_openrouter_models();
|
|
13702
|
+
init_openrouter_research();
|
|
13703
|
+
init_openrouter_research_models();
|
|
12259
13704
|
init_openrouter_speech_models();
|
|
12260
13705
|
init_utils();
|
|
12261
13706
|
logger = createLogger({ name: "openrouter" });
|
|
@@ -12275,6 +13720,66 @@ var init_openrouter = __esm({
|
|
|
12275
13720
|
getModelSpecs() {
|
|
12276
13721
|
return OPENROUTER_MODELS;
|
|
12277
13722
|
}
|
|
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
|
+
}
|
|
12278
13783
|
/**
|
|
12279
13784
|
* Override buildApiRequest to inject reasoning parameters and cache_control breakpoints.
|
|
12280
13785
|
* OpenRouter normalizes reasoning into the standard OpenAI format,
|
|
@@ -12574,6 +14079,590 @@ var init_discovery = __esm({
|
|
|
12574
14079
|
}
|
|
12575
14080
|
});
|
|
12576
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.`
|
|
14533
|
+
);
|
|
14534
|
+
}
|
|
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.`
|
|
14548
|
+
);
|
|
14549
|
+
}
|
|
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.`
|
|
14561
|
+
);
|
|
14562
|
+
}
|
|
14563
|
+
return adapter.cancelResearch(ref);
|
|
14564
|
+
}
|
|
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;
|
|
14572
|
+
}
|
|
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(", ");
|
|
14598
|
+
}
|
|
14599
|
+
/**
|
|
14600
|
+
* Pre-flight validation against the catalog spec — fails fast before any
|
|
14601
|
+
* network call and applies spec-driven defaults.
|
|
14602
|
+
*/
|
|
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.`
|
|
14609
|
+
);
|
|
14610
|
+
}
|
|
14611
|
+
if (options.previousJobId && !spec.capabilities.followUps) {
|
|
14612
|
+
throw new ResearchValidationError(
|
|
14613
|
+
`Model "${spec.modelId}" does not support follow-up research runs (previousJobId).`
|
|
14614
|
+
);
|
|
14615
|
+
}
|
|
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.")
|
|
14627
|
+
);
|
|
14628
|
+
}
|
|
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
|
|
14653
|
+
});
|
|
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}".` : ".")
|
|
14659
|
+
);
|
|
14660
|
+
}
|
|
14661
|
+
}
|
|
14662
|
+
};
|
|
14663
|
+
}
|
|
14664
|
+
});
|
|
14665
|
+
|
|
12577
14666
|
// src/core/model-registry.ts
|
|
12578
14667
|
var ModelRegistry;
|
|
12579
14668
|
var init_model_registry = __esm({
|
|
@@ -12798,6 +14887,8 @@ var init_image = __esm({
|
|
|
12798
14887
|
this.adapters = adapters;
|
|
12799
14888
|
this.defaultProvider = defaultProvider;
|
|
12800
14889
|
}
|
|
14890
|
+
adapters;
|
|
14891
|
+
defaultProvider;
|
|
12801
14892
|
/**
|
|
12802
14893
|
* Generate images from a text prompt.
|
|
12803
14894
|
*
|
|
@@ -12850,6 +14941,8 @@ var init_speech = __esm({
|
|
|
12850
14941
|
this.adapters = adapters;
|
|
12851
14942
|
this.defaultProvider = defaultProvider;
|
|
12852
14943
|
}
|
|
14944
|
+
adapters;
|
|
14945
|
+
defaultProvider;
|
|
12853
14946
|
/**
|
|
12854
14947
|
* Generate speech audio from text.
|
|
12855
14948
|
*
|
|
@@ -12945,6 +15038,7 @@ var init_text = __esm({
|
|
|
12945
15038
|
constructor(client) {
|
|
12946
15039
|
this.client = client;
|
|
12947
15040
|
}
|
|
15041
|
+
client;
|
|
12948
15042
|
/**
|
|
12949
15043
|
* Generate a complete text response.
|
|
12950
15044
|
*
|
|
@@ -12980,6 +15074,7 @@ var init_vision = __esm({
|
|
|
12980
15074
|
constructor(client) {
|
|
12981
15075
|
this.client = client;
|
|
12982
15076
|
}
|
|
15077
|
+
client;
|
|
12983
15078
|
/**
|
|
12984
15079
|
* Build a message builder with the image content attached.
|
|
12985
15080
|
* Handles URLs, data URLs, base64 strings, and binary buffers.
|
|
@@ -13106,6 +15201,7 @@ var init_options = __esm({
|
|
|
13106
15201
|
constructor(defaultProvider = "openai") {
|
|
13107
15202
|
this.defaultProvider = defaultProvider;
|
|
13108
15203
|
}
|
|
15204
|
+
defaultProvider;
|
|
13109
15205
|
parse(identifier) {
|
|
13110
15206
|
const trimmed = identifier.trim();
|
|
13111
15207
|
if (!trimmed) {
|
|
@@ -13137,6 +15233,7 @@ var init_client = __esm({
|
|
|
13137
15233
|
"use strict";
|
|
13138
15234
|
init_builder();
|
|
13139
15235
|
init_discovery();
|
|
15236
|
+
init_namespace();
|
|
13140
15237
|
init_constants();
|
|
13141
15238
|
init_model_registry();
|
|
13142
15239
|
init_image();
|
|
@@ -13155,6 +15252,12 @@ var init_client = __esm({
|
|
|
13155
15252
|
image;
|
|
13156
15253
|
speech;
|
|
13157
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;
|
|
13158
15261
|
constructor(...args) {
|
|
13159
15262
|
let adapters = [];
|
|
13160
15263
|
let defaultProvider;
|
|
@@ -13206,6 +15309,7 @@ var init_client = __esm({
|
|
|
13206
15309
|
this.image = new ImageNamespace(this.adapters, this.defaultProvider);
|
|
13207
15310
|
this.speech = new SpeechNamespace(this.adapters, this.defaultProvider);
|
|
13208
15311
|
this.vision = new VisionNamespace(this);
|
|
15312
|
+
this.research = new ResearchNamespace(this.adapters, this.parser);
|
|
13209
15313
|
}
|
|
13210
15314
|
stream(options) {
|
|
13211
15315
|
const descriptor = this.parser.parse(options.model);
|
|
@@ -13472,8 +15576,8 @@ var init_builder = __esm({
|
|
|
13472
15576
|
return this;
|
|
13473
15577
|
}
|
|
13474
15578
|
/** Set maximum iterations. */
|
|
13475
|
-
withMaxIterations(
|
|
13476
|
-
this.core.maxIterations =
|
|
15579
|
+
withMaxIterations(max2) {
|
|
15580
|
+
this.core.maxIterations = max2;
|
|
13477
15581
|
return this;
|
|
13478
15582
|
}
|
|
13479
15583
|
/** Set the budget limit in USD. */
|
|
@@ -13602,10 +15706,10 @@ var init_builder = __esm({
|
|
|
13602
15706
|
return this;
|
|
13603
15707
|
}
|
|
13604
15708
|
/** Set the maximum number of gadgets to execute per LLM response. */
|
|
13605
|
-
withMaxGadgetsPerResponse(
|
|
13606
|
-
if (
|
|
13607
|
-
if (!Number.isInteger(
|
|
13608
|
-
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;
|
|
13609
15713
|
return this;
|
|
13610
15714
|
}
|
|
13611
15715
|
/** Enable or disable gadget output limiting. */
|
|
@@ -14306,6 +16410,8 @@ var init_cost_reporting_client = __esm({
|
|
|
14306
16410
|
}
|
|
14307
16411
|
};
|
|
14308
16412
|
}
|
|
16413
|
+
client;
|
|
16414
|
+
reportCost;
|
|
14309
16415
|
image;
|
|
14310
16416
|
speech;
|
|
14311
16417
|
/**
|
|
@@ -17063,7 +19169,7 @@ var init_stream_processor_factory = __esm({
|
|
|
17063
19169
|
|
|
17064
19170
|
// src/mcp/errors.ts
|
|
17065
19171
|
var McpError, McpUntrustedCommandError, McpConnectError, McpToolCallError, McpTimeoutError, JsonSchemaConversionError;
|
|
17066
|
-
var
|
|
19172
|
+
var init_errors3 = __esm({
|
|
17067
19173
|
"src/mcp/errors.ts"() {
|
|
17068
19174
|
"use strict";
|
|
17069
19175
|
McpError = class extends Error {
|
|
@@ -17149,7 +19255,7 @@ var init_allowlist = __esm({
|
|
|
17149
19255
|
"src/mcp/allowlist.ts"() {
|
|
17150
19256
|
"use strict";
|
|
17151
19257
|
import_node_path6 = __toESM(require("path"), 1);
|
|
17152
|
-
|
|
19258
|
+
init_errors3();
|
|
17153
19259
|
DEFAULT_MCP_COMMAND_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
17154
19260
|
"npx",
|
|
17155
19261
|
"node",
|
|
@@ -17187,7 +19293,7 @@ var init_client2 = __esm({
|
|
|
17187
19293
|
"src/mcp/client.ts"() {
|
|
17188
19294
|
"use strict";
|
|
17189
19295
|
init_allowlist();
|
|
17190
|
-
|
|
19296
|
+
init_errors3();
|
|
17191
19297
|
cachedSdk = null;
|
|
17192
19298
|
DEFAULT_CLIENT_INFO = { name: "llmist", version: "0.0.0" };
|
|
17193
19299
|
McpClient = class {
|
|
@@ -17196,6 +19302,7 @@ var init_client2 = __esm({
|
|
|
17196
19302
|
this.injectedTransport = opts?.transport;
|
|
17197
19303
|
this.clientInfo = opts?.clientInfo ?? DEFAULT_CLIENT_INFO;
|
|
17198
19304
|
}
|
|
19305
|
+
spec;
|
|
17199
19306
|
sdkClient = null;
|
|
17200
19307
|
spawnedPid = null;
|
|
17201
19308
|
closed = false;
|
|
@@ -17769,7 +19876,7 @@ var init_json_schema_to_zod = __esm({
|
|
|
17769
19876
|
"src/mcp/json-schema-to-zod.ts"() {
|
|
17770
19877
|
"use strict";
|
|
17771
19878
|
import_zod4 = require("zod");
|
|
17772
|
-
|
|
19879
|
+
init_errors3();
|
|
17773
19880
|
}
|
|
17774
19881
|
});
|
|
17775
19882
|
|
|
@@ -18809,7 +20916,17 @@ __export(index_exports, {
|
|
|
18809
20916
|
OpenAIChatProvider: () => OpenAIChatProvider,
|
|
18810
20917
|
OpenAICompatibleProvider: () => OpenAICompatibleProvider,
|
|
18811
20918
|
OpenRouterProvider: () => OpenRouterProvider,
|
|
20919
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES: () => RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
18812
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,
|
|
18813
20930
|
SimpleSessionManager: () => SimpleSessionManager,
|
|
18814
20931
|
Skill: () => Skill,
|
|
18815
20932
|
SkillRegistry: () => SkillRegistry,
|
|
@@ -18843,6 +20960,7 @@ __export(index_exports, {
|
|
|
18843
20960
|
detectImageMimeType: () => detectImageMimeType,
|
|
18844
20961
|
discoverProviderAdapters: () => discoverProviderAdapters,
|
|
18845
20962
|
discoverSkills: () => discoverSkills,
|
|
20963
|
+
estimateResearchCost: () => estimateResearchCost,
|
|
18846
20964
|
extractMessageText: () => extractMessageText,
|
|
18847
20965
|
extractRetryAfterMs: () => extractRetryAfterMs,
|
|
18848
20966
|
filterByDepth: () => filterByDepth,
|
|
@@ -19122,7 +21240,7 @@ init_typed_gadget();
|
|
|
19122
21240
|
// src/mcp/index.ts
|
|
19123
21241
|
init_allowlist();
|
|
19124
21242
|
init_client2();
|
|
19125
|
-
|
|
21243
|
+
init_errors3();
|
|
19126
21244
|
|
|
19127
21245
|
// src/mcp/gadget-exporter.ts
|
|
19128
21246
|
init_schema_to_json();
|
|
@@ -19363,6 +21481,14 @@ init_tool_adapter();
|
|
|
19363
21481
|
// src/index.ts
|
|
19364
21482
|
init_constants2();
|
|
19365
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
|
+
|
|
19366
21492
|
// src/utils/config-resolver.ts
|
|
19367
21493
|
function resolveValue(ctx, gadgetName, options) {
|
|
19368
21494
|
const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
|
|
@@ -19661,11 +21787,11 @@ var format = {
|
|
|
19661
21787
|
};
|
|
19662
21788
|
|
|
19663
21789
|
// src/utils/timing.ts
|
|
19664
|
-
function randomDelay(min,
|
|
19665
|
-
return Math.floor(Math.random() * (
|
|
21790
|
+
function randomDelay(min, max2) {
|
|
21791
|
+
return Math.floor(Math.random() * (max2 - min + 1)) + min;
|
|
19666
21792
|
}
|
|
19667
|
-
async function humanDelay(min = 50,
|
|
19668
|
-
const delay = randomDelay(min,
|
|
21793
|
+
async function humanDelay(min = 50, max2 = 150) {
|
|
21794
|
+
const delay = randomDelay(min, max2);
|
|
19669
21795
|
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
19670
21796
|
}
|
|
19671
21797
|
async function withTimeout(fn, timeoutMs, signal) {
|
|
@@ -19805,7 +21931,17 @@ function getHostExports2(ctx) {
|
|
|
19805
21931
|
OpenAIChatProvider,
|
|
19806
21932
|
OpenAICompatibleProvider,
|
|
19807
21933
|
OpenRouterProvider,
|
|
21934
|
+
RESEARCH_DATA_SOURCE_TOOL_TYPES,
|
|
19808
21935
|
RateLimitTracker,
|
|
21936
|
+
ResearchDeprecatedModelError,
|
|
21937
|
+
ResearchJobNotResumableError,
|
|
21938
|
+
ResearchNamespace,
|
|
21939
|
+
ResearchNotPollableError,
|
|
21940
|
+
ResearchNotSupportedError,
|
|
21941
|
+
ResearchResultCollector,
|
|
21942
|
+
ResearchStreamConsumedError,
|
|
21943
|
+
ResearchTimeoutError,
|
|
21944
|
+
ResearchValidationError,
|
|
19809
21945
|
SimpleSessionManager,
|
|
19810
21946
|
Skill,
|
|
19811
21947
|
SkillRegistry,
|
|
@@ -19839,6 +21975,7 @@ function getHostExports2(ctx) {
|
|
|
19839
21975
|
detectImageMimeType,
|
|
19840
21976
|
discoverProviderAdapters,
|
|
19841
21977
|
discoverSkills,
|
|
21978
|
+
estimateResearchCost,
|
|
19842
21979
|
extractMessageText,
|
|
19843
21980
|
extractRetryAfterMs,
|
|
19844
21981
|
filterByDepth,
|