@openrouter/sdk 0.1.17 → 0.1.23
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/README.md +0 -8
- package/esm/funcs/callModel.d.ts +77 -0
- package/esm/funcs/callModel.js +100 -0
- package/esm/lib/config.d.ts +10 -2
- package/esm/lib/config.js +2 -2
- package/esm/lib/env.d.ts +13 -0
- package/esm/lib/env.js +16 -0
- package/esm/lib/response-wrapper.d.ts +116 -0
- package/esm/lib/response-wrapper.js +459 -0
- package/esm/lib/reusable-stream.d.ts +39 -0
- package/esm/lib/reusable-stream.js +173 -0
- package/esm/lib/sdks.js +2 -2
- package/esm/lib/stream-transformers.d.ts +47 -0
- package/esm/lib/stream-transformers.js +280 -0
- package/esm/lib/tool-executor.d.ts +53 -0
- package/esm/lib/tool-executor.js +181 -0
- package/esm/lib/tool-orchestrator.d.ts +50 -0
- package/esm/lib/tool-orchestrator.js +132 -0
- package/esm/lib/tool-types.d.ts +199 -0
- package/esm/lib/tool-types.js +32 -0
- package/esm/sdk/sdk.d.ts +10 -0
- package/esm/sdk/sdk.js +9 -0
- package/jsr.json +1 -1
- package/package.json +2 -16
- package/vitest.config.ts +4 -0
- package/REACT_QUERY.md +0 -296
- package/esm/react-query/_context.d.ts +0 -8
- package/esm/react-query/_context.js +0 -14
- package/esm/react-query/_types.d.ts +0 -27
- package/esm/react-query/_types.js +0 -5
- package/esm/react-query/analyticsGetUserActivity.d.ts +0 -36
- package/esm/react-query/analyticsGetUserActivity.js +0 -77
- package/esm/react-query/apiKeysCreate.d.ts +0 -20
- package/esm/react-query/apiKeysCreate.js +0 -39
- package/esm/react-query/apiKeysDelete.d.ts +0 -20
- package/esm/react-query/apiKeysDelete.js +0 -39
- package/esm/react-query/apiKeysGet.d.ts +0 -24
- package/esm/react-query/apiKeysGet.js +0 -66
- package/esm/react-query/apiKeysGetCurrentKeyMetadata.d.ts +0 -29
- package/esm/react-query/apiKeysGetCurrentKeyMetadata.js +0 -66
- package/esm/react-query/apiKeysList.d.ts +0 -37
- package/esm/react-query/apiKeysList.js +0 -69
- package/esm/react-query/apiKeysUpdate.d.ts +0 -20
- package/esm/react-query/apiKeysUpdate.js +0 -39
- package/esm/react-query/betaResponsesSend.d.ts +0 -24
- package/esm/react-query/betaResponsesSend.js +0 -42
- package/esm/react-query/chatSend.d.ts +0 -24
- package/esm/react-query/chatSend.js +0 -42
- package/esm/react-query/completionsGenerate.d.ts +0 -23
- package/esm/react-query/completionsGenerate.js +0 -42
- package/esm/react-query/creditsCreateCoinbaseCharge.d.ts +0 -25
- package/esm/react-query/creditsCreateCoinbaseCharge.js +0 -42
- package/esm/react-query/creditsGetCredits.d.ts +0 -29
- package/esm/react-query/creditsGetCredits.js +0 -66
- package/esm/react-query/embeddingsGenerate.d.ts +0 -23
- package/esm/react-query/embeddingsGenerate.js +0 -42
- package/esm/react-query/embeddingsListModels.d.ts +0 -29
- package/esm/react-query/embeddingsListModels.js +0 -66
- package/esm/react-query/endpointsList.d.ts +0 -24
- package/esm/react-query/endpointsList.js +0 -66
- package/esm/react-query/endpointsListZdrEndpoints.d.ts +0 -23
- package/esm/react-query/endpointsListZdrEndpoints.js +0 -60
- package/esm/react-query/generationsGetGeneration.d.ts +0 -30
- package/esm/react-query/generationsGetGeneration.js +0 -71
- package/esm/react-query/index.d.ts +0 -27
- package/esm/react-query/index.js +0 -30
- package/esm/react-query/modelsCount.d.ts +0 -23
- package/esm/react-query/modelsCount.js +0 -60
- package/esm/react-query/modelsList.d.ts +0 -38
- package/esm/react-query/modelsList.js +0 -69
- package/esm/react-query/modelsListForUser.d.ts +0 -24
- package/esm/react-query/modelsListForUser.js +0 -60
- package/esm/react-query/oAuthCreateAuthCode.d.ts +0 -23
- package/esm/react-query/oAuthCreateAuthCode.js +0 -42
- package/esm/react-query/oAuthExchangeAuthCodeForAPIKey.d.ts +0 -23
- package/esm/react-query/oAuthExchangeAuthCodeForAPIKey.js +0 -42
- package/esm/react-query/parametersGetParameters.d.ts +0 -38
- package/esm/react-query/parametersGetParameters.js +0 -80
- package/esm/react-query/providersList.d.ts +0 -23
- package/esm/react-query/providersList.js +0 -60
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { betaResponsesSend } from "../funcs/betaResponsesSend.js";
|
|
2
|
+
import { ReusableReadableStream } from "./reusable-stream.js";
|
|
3
|
+
import { extractTextDeltas, extractReasoningDeltas, extractToolDeltas, buildMessageStream, consumeStreamForCompletion, extractMessageFromResponse, extractTextFromResponse, extractToolCallsFromResponse, buildToolCallStream, } from "./stream-transformers.js";
|
|
4
|
+
import { hasExecuteFunction, } from "./tool-types.js";
|
|
5
|
+
import { executeTool, } from "./tool-executor.js";
|
|
6
|
+
/**
|
|
7
|
+
* A wrapper around a streaming response that provides multiple consumption patterns.
|
|
8
|
+
*
|
|
9
|
+
* Allows consuming the response in multiple ways:
|
|
10
|
+
* - `await response.getMessage()` - Get the completed message
|
|
11
|
+
* - `await response.getText()` - Get just the text
|
|
12
|
+
* - `for await (const delta of response.getTextStream())` - Stream text deltas
|
|
13
|
+
* - `for await (const msg of response.getNewMessagesStream())` - Stream incremental message updates
|
|
14
|
+
* - `for await (const event of response.getFullResponsesStream())` - Stream all response events
|
|
15
|
+
*
|
|
16
|
+
* All consumption patterns can be used concurrently thanks to the underlying
|
|
17
|
+
* ReusableReadableStream implementation.
|
|
18
|
+
*/
|
|
19
|
+
export class ResponseWrapper {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.reusableStream = null;
|
|
22
|
+
this.streamPromise = null;
|
|
23
|
+
this.messagePromise = null;
|
|
24
|
+
this.textPromise = null;
|
|
25
|
+
this.initPromise = null;
|
|
26
|
+
this.toolExecutionPromise = null;
|
|
27
|
+
this.finalResponse = null;
|
|
28
|
+
this.preliminaryResults = new Map();
|
|
29
|
+
this.allToolExecutionRounds = [];
|
|
30
|
+
this.options = options;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the stream if not already started
|
|
34
|
+
* This is idempotent - multiple calls will return the same promise
|
|
35
|
+
*/
|
|
36
|
+
initStream() {
|
|
37
|
+
if (this.initPromise) {
|
|
38
|
+
return this.initPromise;
|
|
39
|
+
}
|
|
40
|
+
this.initPromise = (async () => {
|
|
41
|
+
// Force stream mode
|
|
42
|
+
const request = { ...this.options.request, stream: true };
|
|
43
|
+
// Create the stream promise
|
|
44
|
+
this.streamPromise = betaResponsesSend(this.options.client, request, this.options.options).then((result) => {
|
|
45
|
+
if (!result.ok) {
|
|
46
|
+
throw result.error;
|
|
47
|
+
}
|
|
48
|
+
return result.value;
|
|
49
|
+
});
|
|
50
|
+
// Wait for the stream and create the reusable stream
|
|
51
|
+
const eventStream = await this.streamPromise;
|
|
52
|
+
this.reusableStream = new ReusableReadableStream(eventStream);
|
|
53
|
+
})();
|
|
54
|
+
return this.initPromise;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute tools automatically if they are provided and have execute functions
|
|
58
|
+
* This is idempotent - multiple calls will return the same promise
|
|
59
|
+
*/
|
|
60
|
+
async executeToolsIfNeeded() {
|
|
61
|
+
if (this.toolExecutionPromise) {
|
|
62
|
+
return this.toolExecutionPromise;
|
|
63
|
+
}
|
|
64
|
+
this.toolExecutionPromise = (async () => {
|
|
65
|
+
await this.initStream();
|
|
66
|
+
if (!this.reusableStream) {
|
|
67
|
+
throw new Error("Stream not initialized");
|
|
68
|
+
}
|
|
69
|
+
// Get the initial response
|
|
70
|
+
const initialResponse = await consumeStreamForCompletion(this.reusableStream);
|
|
71
|
+
// Check if we have tools and if auto-execution is enabled
|
|
72
|
+
const shouldAutoExecute = this.options.tools &&
|
|
73
|
+
this.options.tools.length > 0 &&
|
|
74
|
+
initialResponse.output.some((item) => "type" in item && item.type === "function_call");
|
|
75
|
+
if (!shouldAutoExecute) {
|
|
76
|
+
// No tools to execute, use initial response
|
|
77
|
+
this.finalResponse = initialResponse;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Extract tool calls
|
|
81
|
+
const toolCalls = extractToolCallsFromResponse(initialResponse);
|
|
82
|
+
// Check if any have execute functions
|
|
83
|
+
const executableTools = toolCalls.filter((toolCall) => {
|
|
84
|
+
const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
|
|
85
|
+
return tool && hasExecuteFunction(tool);
|
|
86
|
+
});
|
|
87
|
+
if (executableTools.length === 0) {
|
|
88
|
+
// No executable tools, use initial response
|
|
89
|
+
this.finalResponse = initialResponse;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Get maxToolRounds configuration
|
|
93
|
+
const maxToolRounds = this.options.maxToolRounds ?? 5;
|
|
94
|
+
let currentResponse = initialResponse;
|
|
95
|
+
let currentRound = 0;
|
|
96
|
+
let currentInput = this.options.request.input || [];
|
|
97
|
+
while (true) {
|
|
98
|
+
const currentToolCalls = extractToolCallsFromResponse(currentResponse);
|
|
99
|
+
if (currentToolCalls.length === 0) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
const hasExecutable = currentToolCalls.some((toolCall) => {
|
|
103
|
+
const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
|
|
104
|
+
return tool && hasExecuteFunction(tool);
|
|
105
|
+
});
|
|
106
|
+
if (!hasExecutable) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
// Check if we should continue based on maxToolRounds
|
|
110
|
+
if (typeof maxToolRounds === "number") {
|
|
111
|
+
if (currentRound >= maxToolRounds) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (typeof maxToolRounds === "function") {
|
|
116
|
+
// Function signature: (context: TurnContext) => boolean
|
|
117
|
+
const turnContext = {
|
|
118
|
+
numberOfTurns: currentRound + 1,
|
|
119
|
+
messageHistory: currentInput,
|
|
120
|
+
...(this.options.request.model && { model: this.options.request.model }),
|
|
121
|
+
...(this.options.request.models && { models: this.options.request.models }),
|
|
122
|
+
};
|
|
123
|
+
const shouldContinue = maxToolRounds(turnContext);
|
|
124
|
+
if (!shouldContinue) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Store execution round info
|
|
129
|
+
this.allToolExecutionRounds.push({
|
|
130
|
+
round: currentRound,
|
|
131
|
+
toolCalls: currentToolCalls,
|
|
132
|
+
response: currentResponse,
|
|
133
|
+
});
|
|
134
|
+
// Build turn context for tool execution
|
|
135
|
+
const turnContext = {
|
|
136
|
+
numberOfTurns: currentRound + 1, // 1-indexed
|
|
137
|
+
messageHistory: currentInput,
|
|
138
|
+
...(this.options.request.model && { model: this.options.request.model }),
|
|
139
|
+
...(this.options.request.models && { models: this.options.request.models }),
|
|
140
|
+
};
|
|
141
|
+
// Execute all tool calls
|
|
142
|
+
const toolResults = [];
|
|
143
|
+
for (const toolCall of currentToolCalls) {
|
|
144
|
+
const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
|
|
145
|
+
if (!tool || !hasExecuteFunction(tool)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const result = await executeTool(tool, toolCall, turnContext);
|
|
149
|
+
// Store preliminary results
|
|
150
|
+
if (result.preliminaryResults && result.preliminaryResults.length > 0) {
|
|
151
|
+
this.preliminaryResults.set(toolCall.id, result.preliminaryResults);
|
|
152
|
+
}
|
|
153
|
+
toolResults.push({
|
|
154
|
+
type: "function_call_output",
|
|
155
|
+
id: `output_${toolCall.id}`,
|
|
156
|
+
callId: toolCall.id,
|
|
157
|
+
output: result.error
|
|
158
|
+
? JSON.stringify({ error: result.error.message })
|
|
159
|
+
: JSON.stringify(result.result),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Build new input with tool results
|
|
163
|
+
// For the Responses API, we need to include the tool results in the input
|
|
164
|
+
const newInput = [
|
|
165
|
+
...(Array.isArray(currentResponse.output) ? currentResponse.output : [currentResponse.output]),
|
|
166
|
+
...toolResults,
|
|
167
|
+
];
|
|
168
|
+
// Update current input for next iteration
|
|
169
|
+
currentInput = newInput;
|
|
170
|
+
// Make new request with tool results
|
|
171
|
+
const newRequest = {
|
|
172
|
+
...this.options.request,
|
|
173
|
+
input: newInput,
|
|
174
|
+
stream: false,
|
|
175
|
+
};
|
|
176
|
+
const newResult = await betaResponsesSend(this.options.client, newRequest, this.options.options);
|
|
177
|
+
if (!newResult.ok) {
|
|
178
|
+
throw newResult.error;
|
|
179
|
+
}
|
|
180
|
+
// Handle the result - it might be a stream or a response
|
|
181
|
+
const value = newResult.value;
|
|
182
|
+
if (value && typeof value === "object" && "toReadableStream" in value) {
|
|
183
|
+
// It's a stream, consume it
|
|
184
|
+
const stream = new ReusableReadableStream(value);
|
|
185
|
+
currentResponse = await consumeStreamForCompletion(stream);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
currentResponse = value;
|
|
189
|
+
}
|
|
190
|
+
currentRound++;
|
|
191
|
+
}
|
|
192
|
+
this.finalResponse = currentResponse;
|
|
193
|
+
})();
|
|
194
|
+
return this.toolExecutionPromise;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get the completed message from the response.
|
|
198
|
+
* This will consume the stream until completion, execute any tools, and extract the first message.
|
|
199
|
+
* Returns an AssistantMessage in chat format.
|
|
200
|
+
*/
|
|
201
|
+
getMessage() {
|
|
202
|
+
if (this.messagePromise) {
|
|
203
|
+
return this.messagePromise;
|
|
204
|
+
}
|
|
205
|
+
this.messagePromise = (async () => {
|
|
206
|
+
await this.executeToolsIfNeeded();
|
|
207
|
+
if (!this.finalResponse) {
|
|
208
|
+
throw new Error("Response not available");
|
|
209
|
+
}
|
|
210
|
+
return extractMessageFromResponse(this.finalResponse);
|
|
211
|
+
})();
|
|
212
|
+
return this.messagePromise;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get just the text content from the response.
|
|
216
|
+
* This will consume the stream until completion, execute any tools, and extract the text.
|
|
217
|
+
*/
|
|
218
|
+
getText() {
|
|
219
|
+
if (this.textPromise) {
|
|
220
|
+
return this.textPromise;
|
|
221
|
+
}
|
|
222
|
+
this.textPromise = (async () => {
|
|
223
|
+
await this.executeToolsIfNeeded();
|
|
224
|
+
if (!this.finalResponse) {
|
|
225
|
+
throw new Error("Response not available");
|
|
226
|
+
}
|
|
227
|
+
return extractTextFromResponse(this.finalResponse);
|
|
228
|
+
})();
|
|
229
|
+
return this.textPromise;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Stream all response events as they arrive.
|
|
233
|
+
* Multiple consumers can iterate over this stream concurrently.
|
|
234
|
+
* Includes preliminary tool result events after tool execution.
|
|
235
|
+
*/
|
|
236
|
+
getFullResponsesStream() {
|
|
237
|
+
return (async function* () {
|
|
238
|
+
await this.initStream();
|
|
239
|
+
if (!this.reusableStream) {
|
|
240
|
+
throw new Error("Stream not initialized");
|
|
241
|
+
}
|
|
242
|
+
const consumer = this.reusableStream.createConsumer();
|
|
243
|
+
// Yield original events directly
|
|
244
|
+
for await (const event of consumer) {
|
|
245
|
+
yield event;
|
|
246
|
+
}
|
|
247
|
+
// After stream completes, check if tools were executed and emit preliminary results
|
|
248
|
+
await this.executeToolsIfNeeded();
|
|
249
|
+
// Emit all preliminary results as new event types
|
|
250
|
+
for (const [toolCallId, results] of this.preliminaryResults) {
|
|
251
|
+
for (const result of results) {
|
|
252
|
+
yield {
|
|
253
|
+
type: "tool.preliminary_result",
|
|
254
|
+
toolCallId,
|
|
255
|
+
result,
|
|
256
|
+
timestamp: Date.now(),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}.call(this));
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Stream only text deltas as they arrive.
|
|
264
|
+
* This filters the full event stream to only yield text content.
|
|
265
|
+
*/
|
|
266
|
+
getTextStream() {
|
|
267
|
+
return (async function* () {
|
|
268
|
+
await this.initStream();
|
|
269
|
+
if (!this.reusableStream) {
|
|
270
|
+
throw new Error("Stream not initialized");
|
|
271
|
+
}
|
|
272
|
+
yield* extractTextDeltas(this.reusableStream);
|
|
273
|
+
}.call(this));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Stream incremental message updates as content is added.
|
|
277
|
+
* Each iteration yields an updated version of the message with new content.
|
|
278
|
+
* Also yields ToolResponseMessages after tool execution completes.
|
|
279
|
+
* Returns AssistantMessage or ToolResponseMessage in chat format.
|
|
280
|
+
*/
|
|
281
|
+
getNewMessagesStream() {
|
|
282
|
+
return (async function* () {
|
|
283
|
+
await this.initStream();
|
|
284
|
+
if (!this.reusableStream) {
|
|
285
|
+
throw new Error("Stream not initialized");
|
|
286
|
+
}
|
|
287
|
+
// First yield assistant messages from the stream
|
|
288
|
+
yield* buildMessageStream(this.reusableStream);
|
|
289
|
+
// Execute tools if needed
|
|
290
|
+
await this.executeToolsIfNeeded();
|
|
291
|
+
// Yield tool response messages for each executed tool
|
|
292
|
+
for (const round of this.allToolExecutionRounds) {
|
|
293
|
+
for (const toolCall of round.toolCalls) {
|
|
294
|
+
// Find the tool to check if it was executed
|
|
295
|
+
const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
|
|
296
|
+
if (!tool || !hasExecuteFunction(tool)) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// Get the result from preliminary results or construct from the response
|
|
300
|
+
const prelimResults = this.preliminaryResults.get(toolCall.id);
|
|
301
|
+
const result = prelimResults && prelimResults.length > 0
|
|
302
|
+
? prelimResults[prelimResults.length - 1] // Last result is the final output
|
|
303
|
+
: undefined;
|
|
304
|
+
// Yield tool response message
|
|
305
|
+
yield {
|
|
306
|
+
role: "tool",
|
|
307
|
+
content: result !== undefined ? JSON.stringify(result) : "",
|
|
308
|
+
toolCallId: toolCall.id,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// If tools were executed, yield the final assistant message (if there is one)
|
|
313
|
+
if (this.finalResponse && this.allToolExecutionRounds.length > 0) {
|
|
314
|
+
// Check if the final response contains a message
|
|
315
|
+
const hasMessage = this.finalResponse.output.some((item) => "type" in item && item.type === "message");
|
|
316
|
+
if (hasMessage) {
|
|
317
|
+
yield extractMessageFromResponse(this.finalResponse);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}.call(this));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Stream only reasoning deltas as they arrive.
|
|
324
|
+
* This filters the full event stream to only yield reasoning content.
|
|
325
|
+
*/
|
|
326
|
+
getReasoningStream() {
|
|
327
|
+
return (async function* () {
|
|
328
|
+
await this.initStream();
|
|
329
|
+
if (!this.reusableStream) {
|
|
330
|
+
throw new Error("Stream not initialized");
|
|
331
|
+
}
|
|
332
|
+
yield* extractReasoningDeltas(this.reusableStream);
|
|
333
|
+
}.call(this));
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Stream tool call argument deltas and preliminary results.
|
|
337
|
+
* This filters the full event stream to yield:
|
|
338
|
+
* - Tool call argument deltas as { type: "delta", content: string }
|
|
339
|
+
* - Preliminary results as { type: "preliminary_result", toolCallId, result }
|
|
340
|
+
*/
|
|
341
|
+
getToolStream() {
|
|
342
|
+
return (async function* () {
|
|
343
|
+
await this.initStream();
|
|
344
|
+
if (!this.reusableStream) {
|
|
345
|
+
throw new Error("Stream not initialized");
|
|
346
|
+
}
|
|
347
|
+
// Yield tool deltas as structured events
|
|
348
|
+
for await (const delta of extractToolDeltas(this.reusableStream)) {
|
|
349
|
+
yield { type: "delta", content: delta };
|
|
350
|
+
}
|
|
351
|
+
// After stream completes, check if tools were executed and emit preliminary results
|
|
352
|
+
await this.executeToolsIfNeeded();
|
|
353
|
+
// Emit all preliminary results
|
|
354
|
+
for (const [toolCallId, results] of this.preliminaryResults) {
|
|
355
|
+
for (const result of results) {
|
|
356
|
+
yield {
|
|
357
|
+
type: "preliminary_result",
|
|
358
|
+
toolCallId,
|
|
359
|
+
result,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}.call(this));
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Stream events in chat format (compatibility layer).
|
|
367
|
+
* Note: This transforms responses API events into a chat-like format.
|
|
368
|
+
* Includes preliminary tool result events after tool execution.
|
|
369
|
+
*
|
|
370
|
+
* @remarks
|
|
371
|
+
* This is a compatibility method that attempts to transform the responses API
|
|
372
|
+
* stream into a format similar to the chat API. Due to differences in the APIs,
|
|
373
|
+
* this may not be a perfect mapping.
|
|
374
|
+
*/
|
|
375
|
+
getFullChatStream() {
|
|
376
|
+
return (async function* () {
|
|
377
|
+
await this.initStream();
|
|
378
|
+
if (!this.reusableStream) {
|
|
379
|
+
throw new Error("Stream not initialized");
|
|
380
|
+
}
|
|
381
|
+
const consumer = this.reusableStream.createConsumer();
|
|
382
|
+
for await (const event of consumer) {
|
|
383
|
+
if (!("type" in event))
|
|
384
|
+
continue;
|
|
385
|
+
// Transform responses events to chat-like format
|
|
386
|
+
// This is a simplified transformation - you may need to adjust based on your needs
|
|
387
|
+
if (event.type === "response.output_text.delta") {
|
|
388
|
+
const deltaEvent = event;
|
|
389
|
+
yield {
|
|
390
|
+
type: "content.delta",
|
|
391
|
+
delta: deltaEvent.delta,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
else if (event.type === "response.completed") {
|
|
395
|
+
const completedEvent = event;
|
|
396
|
+
yield {
|
|
397
|
+
type: "message.complete",
|
|
398
|
+
response: completedEvent.response,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
// Pass through other events
|
|
403
|
+
yield {
|
|
404
|
+
type: event.type,
|
|
405
|
+
event,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// After stream completes, check if tools were executed and emit preliminary results
|
|
410
|
+
await this.executeToolsIfNeeded();
|
|
411
|
+
// Emit all preliminary results
|
|
412
|
+
for (const [toolCallId, results] of this.preliminaryResults) {
|
|
413
|
+
for (const result of results) {
|
|
414
|
+
yield {
|
|
415
|
+
type: "tool.preliminary_result",
|
|
416
|
+
toolCallId,
|
|
417
|
+
result,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}.call(this));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get all tool calls from the completed response (before auto-execution).
|
|
425
|
+
* Note: If tools have execute functions, they will be automatically executed
|
|
426
|
+
* and this will return the tool calls from the initial response.
|
|
427
|
+
* Returns structured tool calls with parsed arguments.
|
|
428
|
+
*/
|
|
429
|
+
async getToolCalls() {
|
|
430
|
+
await this.initStream();
|
|
431
|
+
if (!this.reusableStream) {
|
|
432
|
+
throw new Error("Stream not initialized");
|
|
433
|
+
}
|
|
434
|
+
const completedResponse = await consumeStreamForCompletion(this.reusableStream);
|
|
435
|
+
return extractToolCallsFromResponse(completedResponse);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Stream structured tool call objects as they're completed.
|
|
439
|
+
* Each iteration yields a complete tool call with parsed arguments.
|
|
440
|
+
*/
|
|
441
|
+
getToolCallsStream() {
|
|
442
|
+
return (async function* () {
|
|
443
|
+
await this.initStream();
|
|
444
|
+
if (!this.reusableStream) {
|
|
445
|
+
throw new Error("Stream not initialized");
|
|
446
|
+
}
|
|
447
|
+
yield* buildToolCallStream(this.reusableStream);
|
|
448
|
+
}.call(this));
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Cancel the underlying stream and all consumers
|
|
452
|
+
*/
|
|
453
|
+
async cancel() {
|
|
454
|
+
if (this.reusableStream) {
|
|
455
|
+
await this.reusableStream.cancel();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
//# sourceMappingURL=response-wrapper.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reusable readable stream that allows multiple consumers to read from the same source stream
|
|
3
|
+
* concurrently while it's actively streaming, without forcing consumers to wait for full buffering.
|
|
4
|
+
*
|
|
5
|
+
* Key features:
|
|
6
|
+
* - Multiple concurrent consumers with independent read positions
|
|
7
|
+
* - New consumers can attach while streaming is active
|
|
8
|
+
* - Efficient memory management with automatic cleanup
|
|
9
|
+
* - Each consumer can read at their own pace
|
|
10
|
+
*/
|
|
11
|
+
export declare class ReusableReadableStream<T> {
|
|
12
|
+
private sourceStream;
|
|
13
|
+
private buffer;
|
|
14
|
+
private consumers;
|
|
15
|
+
private nextConsumerId;
|
|
16
|
+
private sourceReader;
|
|
17
|
+
private sourceComplete;
|
|
18
|
+
private sourceError;
|
|
19
|
+
private pumpStarted;
|
|
20
|
+
constructor(sourceStream: ReadableStream<T>);
|
|
21
|
+
/**
|
|
22
|
+
* Create a new consumer that can independently iterate over the stream.
|
|
23
|
+
* Multiple consumers can be created and will all receive the same data.
|
|
24
|
+
*/
|
|
25
|
+
createConsumer(): AsyncIterableIterator<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Start pumping data from the source stream into the buffer
|
|
28
|
+
*/
|
|
29
|
+
private startPump;
|
|
30
|
+
/**
|
|
31
|
+
* Notify all waiting consumers that new data is available
|
|
32
|
+
*/
|
|
33
|
+
private notifyAllConsumers;
|
|
34
|
+
/**
|
|
35
|
+
* Cancel the source stream and all consumers
|
|
36
|
+
*/
|
|
37
|
+
cancel(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=reusable-stream.d.ts.map
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reusable readable stream that allows multiple consumers to read from the same source stream
|
|
3
|
+
* concurrently while it's actively streaming, without forcing consumers to wait for full buffering.
|
|
4
|
+
*
|
|
5
|
+
* Key features:
|
|
6
|
+
* - Multiple concurrent consumers with independent read positions
|
|
7
|
+
* - New consumers can attach while streaming is active
|
|
8
|
+
* - Efficient memory management with automatic cleanup
|
|
9
|
+
* - Each consumer can read at their own pace
|
|
10
|
+
*/
|
|
11
|
+
export class ReusableReadableStream {
|
|
12
|
+
constructor(sourceStream) {
|
|
13
|
+
this.sourceStream = sourceStream;
|
|
14
|
+
this.buffer = [];
|
|
15
|
+
this.consumers = new Map();
|
|
16
|
+
this.nextConsumerId = 0;
|
|
17
|
+
this.sourceReader = null;
|
|
18
|
+
this.sourceComplete = false;
|
|
19
|
+
this.sourceError = null;
|
|
20
|
+
this.pumpStarted = false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create a new consumer that can independently iterate over the stream.
|
|
24
|
+
* Multiple consumers can be created and will all receive the same data.
|
|
25
|
+
*/
|
|
26
|
+
createConsumer() {
|
|
27
|
+
const consumerId = this.nextConsumerId++;
|
|
28
|
+
const state = {
|
|
29
|
+
position: 0,
|
|
30
|
+
waitingPromise: null,
|
|
31
|
+
cancelled: false,
|
|
32
|
+
};
|
|
33
|
+
this.consumers.set(consumerId, state);
|
|
34
|
+
// Start pumping the source stream if not already started
|
|
35
|
+
if (!this.pumpStarted) {
|
|
36
|
+
this.startPump();
|
|
37
|
+
}
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
39
|
+
const self = this;
|
|
40
|
+
return {
|
|
41
|
+
async next() {
|
|
42
|
+
const consumer = self.consumers.get(consumerId);
|
|
43
|
+
if (!consumer) {
|
|
44
|
+
return { done: true, value: undefined };
|
|
45
|
+
}
|
|
46
|
+
if (consumer.cancelled) {
|
|
47
|
+
return { done: true, value: undefined };
|
|
48
|
+
}
|
|
49
|
+
// If we have buffered data at this position, return it
|
|
50
|
+
if (consumer.position < self.buffer.length) {
|
|
51
|
+
const value = self.buffer[consumer.position];
|
|
52
|
+
consumer.position++;
|
|
53
|
+
// Note: We don't clean up buffer to allow sequential/reusable access
|
|
54
|
+
return { done: false, value };
|
|
55
|
+
}
|
|
56
|
+
// If source is complete and we've read everything, we're done
|
|
57
|
+
if (self.sourceComplete) {
|
|
58
|
+
self.consumers.delete(consumerId);
|
|
59
|
+
return { done: true, value: undefined };
|
|
60
|
+
}
|
|
61
|
+
// If source had an error, propagate it
|
|
62
|
+
if (self.sourceError) {
|
|
63
|
+
self.consumers.delete(consumerId);
|
|
64
|
+
throw self.sourceError;
|
|
65
|
+
}
|
|
66
|
+
// Wait for more data - but check conditions after setting up the promise
|
|
67
|
+
// to avoid race condition where source completes between check and wait
|
|
68
|
+
const waitPromise = new Promise((resolve, reject) => {
|
|
69
|
+
consumer.waitingPromise = { resolve, reject };
|
|
70
|
+
});
|
|
71
|
+
// Double-check conditions after setting up promise to handle race
|
|
72
|
+
if (self.sourceComplete || self.sourceError || consumer.position < self.buffer.length) {
|
|
73
|
+
// Resolve immediately if conditions changed
|
|
74
|
+
if (consumer.waitingPromise) {
|
|
75
|
+
consumer.waitingPromise.resolve();
|
|
76
|
+
consumer.waitingPromise = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await waitPromise;
|
|
80
|
+
// Recursively try again after waking up
|
|
81
|
+
return this.next();
|
|
82
|
+
},
|
|
83
|
+
async return() {
|
|
84
|
+
const consumer = self.consumers.get(consumerId);
|
|
85
|
+
if (consumer) {
|
|
86
|
+
consumer.cancelled = true;
|
|
87
|
+
self.consumers.delete(consumerId);
|
|
88
|
+
}
|
|
89
|
+
return { done: true, value: undefined };
|
|
90
|
+
},
|
|
91
|
+
async throw(e) {
|
|
92
|
+
const consumer = self.consumers.get(consumerId);
|
|
93
|
+
if (consumer) {
|
|
94
|
+
consumer.cancelled = true;
|
|
95
|
+
self.consumers.delete(consumerId);
|
|
96
|
+
}
|
|
97
|
+
throw e;
|
|
98
|
+
},
|
|
99
|
+
[Symbol.asyncIterator]() {
|
|
100
|
+
return this;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Start pumping data from the source stream into the buffer
|
|
106
|
+
*/
|
|
107
|
+
startPump() {
|
|
108
|
+
if (this.pumpStarted)
|
|
109
|
+
return;
|
|
110
|
+
this.pumpStarted = true;
|
|
111
|
+
this.sourceReader = this.sourceStream.getReader();
|
|
112
|
+
void (async () => {
|
|
113
|
+
try {
|
|
114
|
+
while (true) {
|
|
115
|
+
const result = await this.sourceReader.read();
|
|
116
|
+
if (result.done) {
|
|
117
|
+
this.sourceComplete = true;
|
|
118
|
+
this.notifyAllConsumers();
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// Add to buffer
|
|
122
|
+
this.buffer.push(result.value);
|
|
123
|
+
// Notify waiting consumers
|
|
124
|
+
this.notifyAllConsumers();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.sourceError = error instanceof Error ? error : new Error(String(error));
|
|
129
|
+
this.notifyAllConsumers();
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
if (this.sourceReader) {
|
|
133
|
+
this.sourceReader.releaseLock();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Notify all waiting consumers that new data is available
|
|
140
|
+
*/
|
|
141
|
+
notifyAllConsumers() {
|
|
142
|
+
for (const consumer of this.consumers.values()) {
|
|
143
|
+
if (consumer.waitingPromise) {
|
|
144
|
+
if (this.sourceError) {
|
|
145
|
+
consumer.waitingPromise.reject(this.sourceError);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
consumer.waitingPromise.resolve();
|
|
149
|
+
}
|
|
150
|
+
consumer.waitingPromise = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Cancel the source stream and all consumers
|
|
156
|
+
*/
|
|
157
|
+
async cancel() {
|
|
158
|
+
// Cancel all consumers
|
|
159
|
+
for (const consumer of this.consumers.values()) {
|
|
160
|
+
consumer.cancelled = true;
|
|
161
|
+
if (consumer.waitingPromise) {
|
|
162
|
+
consumer.waitingPromise.resolve();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.consumers.clear();
|
|
166
|
+
// Cancel the source stream
|
|
167
|
+
if (this.sourceReader) {
|
|
168
|
+
await this.sourceReader.cancel();
|
|
169
|
+
this.sourceReader.releaseLock();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=reusable-stream.js.map
|
package/esm/lib/sdks.js
CHANGED
|
@@ -19,7 +19,7 @@ import { ERR, OK } from "../types/fp.js";
|
|
|
19
19
|
import { stringToBase64 } from "./base64.js";
|
|
20
20
|
import { SDK_METADATA, serverURLFromOptions } from "./config.js";
|
|
21
21
|
import { encodeForm } from "./encodings.js";
|
|
22
|
-
import { env } from "./env.js";
|
|
22
|
+
import { env, fillGlobals } from "./env.js";
|
|
23
23
|
import { HTTPClient, isAbortError, isConnectionError, isTimeoutError, matchContentType, matchStatusCode, } from "./http.js";
|
|
24
24
|
import { retry } from "./retries.js";
|
|
25
25
|
const gt = typeof globalThis === "undefined" ? null : globalThis;
|
|
@@ -54,7 +54,7 @@ export class ClientSDK {
|
|
|
54
54
|
}
|
|
55
55
|
this._baseURL = url;
|
|
56
56
|
__classPrivateFieldSet(this, _ClientSDK_httpClient, options.httpClient || defaultHttpClient, "f");
|
|
57
|
-
this._options = { ...options, hooks: __classPrivateFieldGet(this, _ClientSDK_hooks, "f") };
|
|
57
|
+
this._options = { ...fillGlobals(options), hooks: __classPrivateFieldGet(this, _ClientSDK_hooks, "f") };
|
|
58
58
|
__classPrivateFieldSet(this, _ClientSDK_logger, this._options.debugLogger, "f");
|
|
59
59
|
if (!__classPrivateFieldGet(this, _ClientSDK_logger, "f") && env().OPENROUTER_DEBUG) {
|
|
60
60
|
__classPrivateFieldSet(this, _ClientSDK_logger, console, "f");
|