@compose-market/sdk 0.8.0 → 0.8.2
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/.speakeasy/a2a.arazzo.yaml +1 -1
- package/.speakeasy/memory.arazzo.yaml +1 -1
- package/.speakeasy/tests.arazzo.yaml +1 -1
- package/generated/inference/esm/lib/config.d.ts +3 -3
- package/generated/inference/esm/lib/config.js +3 -3
- package/generated/inference/package.json +1 -1
- package/generated/inference/src/lib/config.ts +3 -3
- package/generated/manowar/esm/lib/config.d.ts +3 -3
- package/generated/manowar/esm/lib/config.js +3 -3
- package/generated/manowar/package.json +1 -1
- package/generated/manowar/src/lib/config.ts +3 -3
- package/generated/memory/esm/lib/config.d.ts +3 -3
- package/generated/memory/esm/lib/config.js +3 -3
- package/generated/memory/package.json +1 -1
- package/generated/memory/src/lib/config.ts +3 -3
- package/generated/x402/esm/lib/config.d.ts +3 -3
- package/generated/x402/esm/lib/config.js +3 -3
- package/generated/x402/package.json +1 -1
- package/generated/x402/src/lib/config.ts +3 -3
- package/package.json +4 -4
- package/specs/inference.openapi.yaml +1 -1
- package/specs/manowar.openapi.yaml +1 -1
- package/specs/memory.openapi.yaml +1 -1
- package/specs/x402.openapi.yaml +1 -1
- package/dist/errors.d.ts +0 -2
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -2
- package/dist/errors.js.map +0 -1
- package/dist/events.d.ts +0 -202
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js +0 -70
- package/dist/events.js.map +0 -1
- package/dist/index.d.ts +0 -277
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -551
- package/dist/index.js.map +0 -1
- package/dist/resources/accounts.d.ts +0 -32
- package/dist/resources/accounts.d.ts.map +0 -1
- package/dist/resources/accounts.js +0 -128
- package/dist/resources/accounts.js.map +0 -1
- package/dist/resources/agent.d.ts +0 -58
- package/dist/resources/agent.d.ts.map +0 -1
- package/dist/resources/agent.js +0 -770
- package/dist/resources/agent.js.map +0 -1
- package/dist/resources/channels.d.ts +0 -24
- package/dist/resources/channels.d.ts.map +0 -1
- package/dist/resources/channels.js +0 -74
- package/dist/resources/channels.js.map +0 -1
- package/dist/resources/directory.d.ts +0 -43
- package/dist/resources/directory.d.ts.map +0 -1
- package/dist/resources/directory.js +0 -97
- package/dist/resources/directory.js.map +0 -1
- package/dist/resources/dispenser.d.ts +0 -25
- package/dist/resources/dispenser.d.ts.map +0 -1
- package/dist/resources/dispenser.js +0 -56
- package/dist/resources/dispenser.js.map +0 -1
- package/dist/resources/feedback.d.ts +0 -35
- package/dist/resources/feedback.d.ts.map +0 -1
- package/dist/resources/feedback.js +0 -127
- package/dist/resources/feedback.js.map +0 -1
- package/dist/resources/inference.d.ts +0 -206
- package/dist/resources/inference.d.ts.map +0 -1
- package/dist/resources/inference.js +0 -1046
- package/dist/resources/inference.js.map +0 -1
- package/dist/resources/instrumentation.d.ts +0 -29
- package/dist/resources/instrumentation.d.ts.map +0 -1
- package/dist/resources/instrumentation.js +0 -43
- package/dist/resources/instrumentation.js.map +0 -1
- package/dist/resources/keys.d.ts +0 -56
- package/dist/resources/keys.d.ts.map +0 -1
- package/dist/resources/keys.js +0 -186
- package/dist/resources/keys.js.map +0 -1
- package/dist/resources/local.d.ts +0 -56
- package/dist/resources/local.d.ts.map +0 -1
- package/dist/resources/local.js +0 -163
- package/dist/resources/local.js.map +0 -1
- package/dist/resources/memory.d.ts +0 -249
- package/dist/resources/memory.d.ts.map +0 -1
- package/dist/resources/memory.js +0 -217
- package/dist/resources/memory.js.map +0 -1
- package/dist/resources/models.d.ts +0 -45
- package/dist/resources/models.d.ts.map +0 -1
- package/dist/resources/models.js +0 -101
- package/dist/resources/models.js.map +0 -1
- package/dist/resources/permissions.d.ts +0 -23
- package/dist/resources/permissions.d.ts.map +0 -1
- package/dist/resources/permissions.js +0 -69
- package/dist/resources/permissions.js.map +0 -1
- package/dist/resources/receipts.d.ts +0 -23
- package/dist/resources/receipts.d.ts.map +0 -1
- package/dist/resources/receipts.js +0 -48
- package/dist/resources/receipts.js.map +0 -1
- package/dist/resources/session-events.d.ts +0 -51
- package/dist/resources/session-events.d.ts.map +0 -1
- package/dist/resources/session-events.js +0 -404
- package/dist/resources/session-events.js.map +0 -1
- package/dist/resources/settlement.d.ts +0 -21
- package/dist/resources/settlement.d.ts.map +0 -1
- package/dist/resources/settlement.js +0 -30
- package/dist/resources/settlement.js.map +0 -1
- package/dist/resources/system.d.ts +0 -9
- package/dist/resources/system.d.ts.map +0 -1
- package/dist/resources/system.js +0 -19
- package/dist/resources/system.js.map +0 -1
- package/dist/resources/webhooks.d.ts +0 -27
- package/dist/resources/webhooks.d.ts.map +0 -1
- package/dist/resources/webhooks.js +0 -78
- package/dist/resources/webhooks.js.map +0 -1
- package/dist/resources/workflow.d.ts +0 -49
- package/dist/resources/workflow.d.ts.map +0 -1
- package/dist/resources/workflow.js +0 -361
- package/dist/resources/workflow.js.map +0 -1
- package/dist/resources/x402.d.ts +0 -113
- package/dist/resources/x402.d.ts.map +0 -1
- package/dist/resources/x402.js +0 -231
- package/dist/resources/x402.js.map +0 -1
- package/dist/tools/index.d.ts +0 -55
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -51
- package/dist/tools/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -1857
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -14
- package/dist/types/index.js.map +0 -1
- package/dist/version.d.ts +0 -9
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -9
- package/dist/version.js.map +0 -1
|
@@ -1,1046 +0,0 @@
|
|
|
1
|
-
import { AuthenticationError, BadRequestError, Error, PaymentRequiredError } from "../errors.js";
|
|
2
|
-
import { parseSSEStream } from "@compose-market/core/transport";
|
|
3
|
-
import { parseReceiptEvent } from "@compose-market/core/sse/receipt";
|
|
4
|
-
import { decode as decodeModelEvent } from "@compose-market/core/model";
|
|
5
|
-
import { encodePaymentSignature } from "./x402.js";
|
|
6
|
-
import { instrumentBillableResponse, } from "./instrumentation.js";
|
|
7
|
-
export function buildCallHeaders(options, ctxWallet, ctxToken) {
|
|
8
|
-
// When the caller explicitly passes `key: null`, force the raw x402
|
|
9
|
-
// path by suppressing the instance-level token. Any other value (string or
|
|
10
|
-
// undefined) falls back to the instance token.
|
|
11
|
-
const tokenResolved = options?.paymentMode === "x402"
|
|
12
|
-
? null
|
|
13
|
-
: options && "key" in options
|
|
14
|
-
? options.key
|
|
15
|
-
: ctxToken;
|
|
16
|
-
return {
|
|
17
|
-
userAddress: options?.userAddress ?? ctxWallet.address ?? undefined,
|
|
18
|
-
chainId: options?.chainId ?? ctxWallet.chainId ?? undefined,
|
|
19
|
-
key: tokenResolved ?? undefined,
|
|
20
|
-
paymentSignature: options?.paymentSignature ? encodePaymentSignature(options.paymentSignature) : undefined,
|
|
21
|
-
x402MaxAmountWei: options?.x402MaxAmountWei,
|
|
22
|
-
idempotencyKey: options?.idempotencyKey,
|
|
23
|
-
runId: options?.runId,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
function resolvePaymentMode(options) {
|
|
27
|
-
return options?.paymentMode ?? "auto";
|
|
28
|
-
}
|
|
29
|
-
function resolveX402Signer(ctx, options) {
|
|
30
|
-
return options?.x402Signer ?? ctx.getX402SignerMaybe?.() ?? null;
|
|
31
|
-
}
|
|
32
|
-
async function buildX402RetryConfig(error, config, ctx, options) {
|
|
33
|
-
if (!(error instanceof PaymentRequiredError) || !error.paymentRequired) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
if (resolvePaymentMode(options) === "key") {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
const signer = resolveX402Signer(ctx, options);
|
|
40
|
-
if (!signer) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
const wallet = ctx.getWalletMaybe();
|
|
44
|
-
const signed = await signer({
|
|
45
|
-
paymentRequired: error.paymentRequired,
|
|
46
|
-
paymentRequiredHeader: error.paymentRequiredHeader,
|
|
47
|
-
method: config.method,
|
|
48
|
-
path: config.path,
|
|
49
|
-
url: error.paymentRequired.resource.url,
|
|
50
|
-
body: config.body,
|
|
51
|
-
userAddress: options?.userAddress ?? wallet.address,
|
|
52
|
-
chainId: options?.chainId ?? wallet.chainId,
|
|
53
|
-
maxAmountWei: options?.x402MaxAmountWei,
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
...config,
|
|
57
|
-
headers: {
|
|
58
|
-
...(config.headers ?? {}),
|
|
59
|
-
key: null,
|
|
60
|
-
paymentSignature: encodePaymentSignature(signed),
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
function shouldRetryInvalidKeyAsX402(error, ctx, options) {
|
|
65
|
-
return error instanceof AuthenticationError
|
|
66
|
-
&& resolvePaymentMode(options) === "auto"
|
|
67
|
-
&& Boolean(ctx.getTokenMaybe())
|
|
68
|
-
&& Boolean(resolveX402Signer(ctx, options));
|
|
69
|
-
}
|
|
70
|
-
function suppressKey(config) {
|
|
71
|
-
return {
|
|
72
|
-
...config,
|
|
73
|
-
headers: {
|
|
74
|
-
...(config.headers ?? {}),
|
|
75
|
-
key: null,
|
|
76
|
-
paymentSignature: undefined,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
async function requestJsonWithPayment(client, ctx, config, options) {
|
|
81
|
-
try {
|
|
82
|
-
return await client.request(config).withResponse();
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
const retryConfig = await buildX402RetryConfig(error, config, ctx, options);
|
|
86
|
-
if (retryConfig)
|
|
87
|
-
return client.request(retryConfig).withResponse();
|
|
88
|
-
if (shouldRetryInvalidKeyAsX402(error, ctx, options)) {
|
|
89
|
-
const challengeConfig = suppressKey(config);
|
|
90
|
-
try {
|
|
91
|
-
return await client.request(challengeConfig).withResponse();
|
|
92
|
-
}
|
|
93
|
-
catch (challengeError) {
|
|
94
|
-
const signedRetryConfig = await buildX402RetryConfig(challengeError, challengeConfig, ctx, options);
|
|
95
|
-
if (signedRetryConfig)
|
|
96
|
-
return client.request(signedRetryConfig).withResponse();
|
|
97
|
-
throw challengeError;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
export async function requestResponseWithPayment(client, ctx, config, options) {
|
|
104
|
-
try {
|
|
105
|
-
return await client.request(config).asResponse();
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
const retryConfig = await buildX402RetryConfig(error, config, ctx, options);
|
|
109
|
-
if (retryConfig)
|
|
110
|
-
return client.request(retryConfig).asResponse();
|
|
111
|
-
if (shouldRetryInvalidKeyAsX402(error, ctx, options)) {
|
|
112
|
-
const challengeConfig = suppressKey(config);
|
|
113
|
-
try {
|
|
114
|
-
return await client.request(challengeConfig).asResponse();
|
|
115
|
-
}
|
|
116
|
-
catch (challengeError) {
|
|
117
|
-
const signedRetryConfig = await buildX402RetryConfig(challengeError, challengeConfig, ctx, options);
|
|
118
|
-
if (signedRetryConfig)
|
|
119
|
-
return client.request(signedRetryConfig).asResponse();
|
|
120
|
-
throw challengeError;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Read the response header / body pair produced by a billable call, extract
|
|
128
|
-
* receipt + budget + invalid-reason, emit them on the SDK event bus, and
|
|
129
|
-
* return the public `Completion<T>` shape.
|
|
130
|
-
*/
|
|
131
|
-
function toCompletion(ctx, response, data) {
|
|
132
|
-
const result = instrumentBillableResponse(ctx, response, data);
|
|
133
|
-
return {
|
|
134
|
-
data: result.data,
|
|
135
|
-
receipt: result.receipt,
|
|
136
|
-
requestId: result.requestId,
|
|
137
|
-
response: result.response,
|
|
138
|
-
budget: result.budget,
|
|
139
|
-
sessionInvalidReason: result.sessionInvalidReason,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Emit budget / receipt / invalid events for a streaming response. The receipt
|
|
144
|
-
* is attached optionally because streaming calls produce it via the in-band
|
|
145
|
-
* `receipt` SSE frame, not necessarily via the header.
|
|
146
|
-
*/
|
|
147
|
-
function emitStreamingEvents(ctx, response, receipt, requestId) {
|
|
148
|
-
const walletCtx = ctx.getWalletMaybe();
|
|
149
|
-
// Budget / session-invalid headers are set on the initial SSE response
|
|
150
|
-
// (before the stream body), so extracting them from `response` is fine.
|
|
151
|
-
const { extractSessionBudgetFromResponse } = budgetExtractor;
|
|
152
|
-
const { budget, sessionInvalidReason } = extractSessionBudgetFromResponse(response);
|
|
153
|
-
if (receipt) {
|
|
154
|
-
ctx.events.emit("receipt", {
|
|
155
|
-
userAddress: walletCtx.address,
|
|
156
|
-
chainId: walletCtx.chainId,
|
|
157
|
-
receipt,
|
|
158
|
-
requestId,
|
|
159
|
-
source: "stream",
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
if (budget) {
|
|
163
|
-
ctx.events.emit("budget", {
|
|
164
|
-
userAddress: walletCtx.address,
|
|
165
|
-
chainId: walletCtx.chainId,
|
|
166
|
-
snapshot: budget,
|
|
167
|
-
requestId,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (sessionInvalidReason) {
|
|
171
|
-
ctx.events.emit("sessionInvalid", {
|
|
172
|
-
userAddress: walletCtx.address,
|
|
173
|
-
chainId: walletCtx.chainId,
|
|
174
|
-
reason: sessionInvalidReason,
|
|
175
|
-
requestId,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
return { budget, sessionInvalidReason };
|
|
179
|
-
}
|
|
180
|
-
// Lazy-loaded to avoid a circular import through instrumentation.ts -> budget.ts
|
|
181
|
-
// (budget.ts is a pure leaf, so we just import it directly).
|
|
182
|
-
import * as budgetExtractor from "@compose-market/core/sse/budget";
|
|
183
|
-
function bytesToBase64(bytes) {
|
|
184
|
-
if (typeof Buffer !== "undefined") {
|
|
185
|
-
return Buffer.from(bytes).toString("base64");
|
|
186
|
-
}
|
|
187
|
-
let binary = "";
|
|
188
|
-
const chunkSize = 0x8000;
|
|
189
|
-
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
190
|
-
const chunk = bytes.subarray(offset, offset + chunkSize);
|
|
191
|
-
binary += String.fromCharCode(...chunk);
|
|
192
|
-
}
|
|
193
|
-
return globalThis.btoa(binary);
|
|
194
|
-
}
|
|
195
|
-
function isByteArray(value) {
|
|
196
|
-
return value instanceof Uint8Array
|
|
197
|
-
|| Boolean(value
|
|
198
|
-
&& typeof value === "object"
|
|
199
|
-
&& "byteLength" in value
|
|
200
|
-
&& "buffer" in value
|
|
201
|
-
&& typeof value.byteLength === "number");
|
|
202
|
-
}
|
|
203
|
-
async function encodeAudioFile(value) {
|
|
204
|
-
if (typeof value === "string") {
|
|
205
|
-
return value;
|
|
206
|
-
}
|
|
207
|
-
if (typeof Blob !== "undefined" && value instanceof Blob) {
|
|
208
|
-
return bytesToBase64(new Uint8Array(await value.arrayBuffer()));
|
|
209
|
-
}
|
|
210
|
-
if (isByteArray(value)) {
|
|
211
|
-
return bytesToBase64(value);
|
|
212
|
-
}
|
|
213
|
-
throw new BadRequestError({ message: "audio transcriptions: unsupported file type" });
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Async iterable returned by `inference.chat.completions.stream(...)` and
|
|
217
|
-
* `inference.responses.stream(...)`. Exposes `.final()` for consumers that
|
|
218
|
-
* want the reassembled final object + receipt without iterating manually.
|
|
219
|
-
*
|
|
220
|
-
* `for await (const chunk of stream) { ... }` consumes chunks; the generator's
|
|
221
|
-
* return value is captured internally so a subsequent `await stream.final()`
|
|
222
|
-
* resolves to the typed final object even when the caller drove iteration.
|
|
223
|
-
*/
|
|
224
|
-
export class StreamIterator {
|
|
225
|
-
iterator;
|
|
226
|
-
finalResult = null;
|
|
227
|
-
finalSettled = false;
|
|
228
|
-
finalPromise = null;
|
|
229
|
-
constructor(iterator) {
|
|
230
|
-
this.iterator = iterator;
|
|
231
|
-
}
|
|
232
|
-
[Symbol.asyncIterator]() {
|
|
233
|
-
const self = this;
|
|
234
|
-
return {
|
|
235
|
-
next: async () => {
|
|
236
|
-
const r = await self.iterator.next();
|
|
237
|
-
if (r.done) {
|
|
238
|
-
self.finalResult = r.value;
|
|
239
|
-
self.finalSettled = true;
|
|
240
|
-
return { done: true, value: undefined };
|
|
241
|
-
}
|
|
242
|
-
return { done: false, value: r.value };
|
|
243
|
-
},
|
|
244
|
-
return: async () => {
|
|
245
|
-
const r = await self.iterator.return(undefined);
|
|
246
|
-
if (r.done) {
|
|
247
|
-
self.finalResult = r.value;
|
|
248
|
-
self.finalSettled = true;
|
|
249
|
-
}
|
|
250
|
-
return { done: true, value: undefined };
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
async final() {
|
|
255
|
-
if (this.finalSettled)
|
|
256
|
-
return this.finalResult;
|
|
257
|
-
if (this.finalPromise)
|
|
258
|
-
return this.finalPromise;
|
|
259
|
-
this.finalPromise = (async () => {
|
|
260
|
-
let last;
|
|
261
|
-
do {
|
|
262
|
-
last = await this.iterator.next();
|
|
263
|
-
} while (!last.done);
|
|
264
|
-
this.finalResult = last.value;
|
|
265
|
-
this.finalSettled = true;
|
|
266
|
-
return last.value;
|
|
267
|
-
})();
|
|
268
|
-
return this.finalPromise;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// ---------------------------------------------------------------------------
|
|
272
|
-
// Chat completions
|
|
273
|
-
// ---------------------------------------------------------------------------
|
|
274
|
-
function cleanse(params) {
|
|
275
|
-
const { provider: _provider, ...body } = params;
|
|
276
|
-
return body;
|
|
277
|
-
}
|
|
278
|
-
class ChatCompletionsNamespace {
|
|
279
|
-
client;
|
|
280
|
-
ctx;
|
|
281
|
-
constructor(client, ctx) {
|
|
282
|
-
this.client = client;
|
|
283
|
-
this.ctx = ctx;
|
|
284
|
-
}
|
|
285
|
-
async create(params, options) {
|
|
286
|
-
if (params.stream === true) {
|
|
287
|
-
throw new BadRequestError({
|
|
288
|
-
message: "Pass stream: true only to chat.completions.stream(). Use create() for non-streaming calls.",
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
292
|
-
method: "POST",
|
|
293
|
-
path: "/v1/chat/completions",
|
|
294
|
-
body: { ...cleanse(params), stream: false },
|
|
295
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
296
|
-
signal: options?.signal,
|
|
297
|
-
timeoutMs: options?.timeoutMs,
|
|
298
|
-
}, options);
|
|
299
|
-
return toCompletion(this.ctx, response, data);
|
|
300
|
-
}
|
|
301
|
-
stream(params, options) {
|
|
302
|
-
const iterator = streamChatCompletions(this.client, this.ctx, params, options);
|
|
303
|
-
return new StreamIterator(iterator);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
async function* streamChatCompletions(client, ctx, params, options) {
|
|
307
|
-
const response = await requestResponseWithPayment(client, ctx, {
|
|
308
|
-
method: "POST",
|
|
309
|
-
path: "/v1/chat/completions",
|
|
310
|
-
body: { ...cleanse(params), stream: true },
|
|
311
|
-
headers: buildCallHeaders(options, ctx.getWalletMaybe(), ctx.getTokenMaybe()),
|
|
312
|
-
signal: options?.signal,
|
|
313
|
-
timeoutMs: options?.timeoutMs,
|
|
314
|
-
expectStream: true,
|
|
315
|
-
}, options);
|
|
316
|
-
if (!response.body) {
|
|
317
|
-
throw new Error({
|
|
318
|
-
code: "upstream_error",
|
|
319
|
-
message: "Streaming response had no body",
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
let receipt = null;
|
|
323
|
-
const aggregator = buildChatCompletionAggregator(params.model);
|
|
324
|
-
const emittedToolStarts = new Set();
|
|
325
|
-
let streamError = null;
|
|
326
|
-
let sawDone = false;
|
|
327
|
-
try {
|
|
328
|
-
for await (const frame of parseSSEStream(response.body, { signal: options?.signal })) {
|
|
329
|
-
if (frame.event === "receipt") {
|
|
330
|
-
try {
|
|
331
|
-
receipt = parseReceiptEvent(frame.data);
|
|
332
|
-
}
|
|
333
|
-
catch { /* ignore */ }
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (frame.event === "compose.error") {
|
|
337
|
-
try {
|
|
338
|
-
const parsed = JSON.parse(frame.data);
|
|
339
|
-
streamError = new Error({
|
|
340
|
-
code: parsed.code ?? "upstream_error",
|
|
341
|
-
message: parsed.message ?? "Stream error",
|
|
342
|
-
details: parsed.details,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
catch { /* ignore */ }
|
|
346
|
-
const decoded = decodeModelEvent(frame, { runId: options?.runId });
|
|
347
|
-
if (decoded)
|
|
348
|
-
yield decoded;
|
|
349
|
-
continue;
|
|
350
|
-
}
|
|
351
|
-
if (frame.data === "[DONE]") {
|
|
352
|
-
sawDone = true;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (sawDone)
|
|
356
|
-
continue;
|
|
357
|
-
if (frame.event !== "message") {
|
|
358
|
-
// Unknown Compose-specific frames: surface nothing to the chunk stream.
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
let parsed;
|
|
362
|
-
try {
|
|
363
|
-
parsed = JSON.parse(frame.data);
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
aggregator.absorb(parsed);
|
|
369
|
-
// Unified tool-call lifecycle events. The chat stream reports
|
|
370
|
-
// tool_call deltas; each delta with an `id` signals a new call
|
|
371
|
-
// (or continues an existing one). We emit toolCallStart on first
|
|
372
|
-
// sight and toolCallEnd when the chunk's finish_reason is set.
|
|
373
|
-
const walletCtx = ctx.getWalletMaybe();
|
|
374
|
-
const requestId = response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id");
|
|
375
|
-
const deltaToolCalls = parsed.choices?.[0]?.delta?.tool_calls;
|
|
376
|
-
if (Array.isArray(deltaToolCalls)) {
|
|
377
|
-
for (const toolCall of deltaToolCalls) {
|
|
378
|
-
if (toolCall.id && !emittedToolStarts.has(toolCall.id)) {
|
|
379
|
-
emittedToolStarts.add(toolCall.id);
|
|
380
|
-
ctx.events.emit("toolCallStart", {
|
|
381
|
-
userAddress: walletCtx.address,
|
|
382
|
-
chainId: walletCtx.chainId,
|
|
383
|
-
requestId,
|
|
384
|
-
source: "chat",
|
|
385
|
-
toolCallId: toolCall.id,
|
|
386
|
-
toolName: toolCall.function?.name ?? "",
|
|
387
|
-
arguments: toolCall.function?.arguments,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
const finishReason = parsed.choices?.[0]?.finish_reason;
|
|
393
|
-
if (finishReason === "tool_calls") {
|
|
394
|
-
const finalTools = aggregator.snapshotToolCalls();
|
|
395
|
-
for (const tc of finalTools) {
|
|
396
|
-
ctx.events.emit("toolCallEnd", {
|
|
397
|
-
userAddress: walletCtx.address,
|
|
398
|
-
chainId: walletCtx.chainId,
|
|
399
|
-
requestId,
|
|
400
|
-
source: "chat",
|
|
401
|
-
toolCallId: tc.id,
|
|
402
|
-
toolName: tc.function.name,
|
|
403
|
-
arguments: tc.function.arguments,
|
|
404
|
-
failed: false,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
const decoded = decodeModelEvent(parsed, { runId: options?.runId, responseId: parsed.id, model: parsed.model });
|
|
409
|
-
if (decoded)
|
|
410
|
-
yield decoded;
|
|
411
|
-
}
|
|
412
|
-
if (streamError)
|
|
413
|
-
throw streamError;
|
|
414
|
-
const requestId = response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id");
|
|
415
|
-
const { budget, sessionInvalidReason } = emitStreamingEvents(ctx, response, receipt, requestId);
|
|
416
|
-
return {
|
|
417
|
-
chatCompletion: aggregator.finalize(),
|
|
418
|
-
receipt,
|
|
419
|
-
requestId,
|
|
420
|
-
budget,
|
|
421
|
-
sessionInvalidReason,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
finally {
|
|
425
|
-
try {
|
|
426
|
-
response.body?.cancel();
|
|
427
|
-
}
|
|
428
|
-
catch { /* best-effort */ }
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
function buildChatCompletionAggregator(modelHint) {
|
|
432
|
-
let id = "";
|
|
433
|
-
let created = 0;
|
|
434
|
-
let model = modelHint;
|
|
435
|
-
let role = undefined;
|
|
436
|
-
let content = "";
|
|
437
|
-
let reasoningContent = "";
|
|
438
|
-
let finishReason = "stop";
|
|
439
|
-
const toolCalls = new Map();
|
|
440
|
-
let usage;
|
|
441
|
-
return {
|
|
442
|
-
absorb(chunk) {
|
|
443
|
-
id = id || chunk.id;
|
|
444
|
-
created = created || chunk.created;
|
|
445
|
-
model = model || chunk.model;
|
|
446
|
-
if (chunk.usage)
|
|
447
|
-
usage = chunk.usage;
|
|
448
|
-
const choice = chunk.choices?.[0];
|
|
449
|
-
if (!choice)
|
|
450
|
-
return;
|
|
451
|
-
if (choice.finish_reason)
|
|
452
|
-
finishReason = choice.finish_reason;
|
|
453
|
-
const delta = choice.delta;
|
|
454
|
-
if (delta?.role)
|
|
455
|
-
role = delta.role;
|
|
456
|
-
if (typeof delta?.content === "string")
|
|
457
|
-
content += delta.content;
|
|
458
|
-
if (typeof delta?.reasoning_content === "string")
|
|
459
|
-
reasoningContent += delta.reasoning_content;
|
|
460
|
-
if (Array.isArray(delta?.tool_calls)) {
|
|
461
|
-
for (const toolCall of delta.tool_calls) {
|
|
462
|
-
const idx = toolCall.index ?? 0;
|
|
463
|
-
const existing = toolCalls.get(idx) ?? {
|
|
464
|
-
id: toolCall.id ?? `call_${idx}`,
|
|
465
|
-
type: "function",
|
|
466
|
-
function: { name: "", arguments: "" },
|
|
467
|
-
};
|
|
468
|
-
if (toolCall.id)
|
|
469
|
-
existing.id = toolCall.id;
|
|
470
|
-
if (toolCall.function?.name)
|
|
471
|
-
existing.function.name += toolCall.function.name;
|
|
472
|
-
if (toolCall.function?.arguments)
|
|
473
|
-
existing.function.arguments += toolCall.function.arguments;
|
|
474
|
-
toolCalls.set(idx, existing);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
snapshotToolCalls() {
|
|
479
|
-
return Array.from(toolCalls.values());
|
|
480
|
-
},
|
|
481
|
-
finalize() {
|
|
482
|
-
const message = {
|
|
483
|
-
role: role ?? "assistant",
|
|
484
|
-
content,
|
|
485
|
-
};
|
|
486
|
-
if (toolCalls.size > 0) {
|
|
487
|
-
message.tool_calls = Array.from(toolCalls.values());
|
|
488
|
-
}
|
|
489
|
-
if (reasoningContent.length > 0) {
|
|
490
|
-
message.reasoning_content = reasoningContent;
|
|
491
|
-
}
|
|
492
|
-
return {
|
|
493
|
-
id,
|
|
494
|
-
object: "chat.completion",
|
|
495
|
-
created,
|
|
496
|
-
model,
|
|
497
|
-
choices: [
|
|
498
|
-
{
|
|
499
|
-
index: 0,
|
|
500
|
-
message,
|
|
501
|
-
finish_reason: finishReason,
|
|
502
|
-
},
|
|
503
|
-
],
|
|
504
|
-
usage: usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
505
|
-
};
|
|
506
|
-
},
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
// ---------------------------------------------------------------------------
|
|
510
|
-
// Responses API
|
|
511
|
-
// ---------------------------------------------------------------------------
|
|
512
|
-
class ResponsesNamespace {
|
|
513
|
-
client;
|
|
514
|
-
ctx;
|
|
515
|
-
constructor(client, ctx) {
|
|
516
|
-
this.client = client;
|
|
517
|
-
this.ctx = ctx;
|
|
518
|
-
}
|
|
519
|
-
async create(params, options) {
|
|
520
|
-
if (params.stream === true) {
|
|
521
|
-
throw new BadRequestError({
|
|
522
|
-
message: "Pass stream: true only to responses.stream(). Use create() for non-streaming calls.",
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
526
|
-
method: "POST",
|
|
527
|
-
path: "/v1/responses",
|
|
528
|
-
body: { ...cleanse(params), stream: false },
|
|
529
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
530
|
-
signal: options?.signal,
|
|
531
|
-
timeoutMs: options?.timeoutMs,
|
|
532
|
-
}, options);
|
|
533
|
-
return toCompletion(this.ctx, response, data);
|
|
534
|
-
}
|
|
535
|
-
stream(params, options) {
|
|
536
|
-
const iterator = streamResponses(this.client, this.ctx, params, options);
|
|
537
|
-
return new StreamIterator(iterator);
|
|
538
|
-
}
|
|
539
|
-
async get(responseId, options) {
|
|
540
|
-
return this.client.request({
|
|
541
|
-
method: "GET",
|
|
542
|
-
path: `/v1/responses/${encodeURIComponent(responseId)}`,
|
|
543
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
544
|
-
signal: options?.signal,
|
|
545
|
-
timeoutMs: options?.timeoutMs,
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
async inputItems(responseId, options) {
|
|
549
|
-
return this.client.request({
|
|
550
|
-
method: "GET",
|
|
551
|
-
path: `/v1/responses/${encodeURIComponent(responseId)}/input_items`,
|
|
552
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
553
|
-
signal: options?.signal,
|
|
554
|
-
timeoutMs: options?.timeoutMs,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
async append(responseId, params, options) {
|
|
558
|
-
const body = params && typeof params === "object" && !Array.isArray(params)
|
|
559
|
-
? params
|
|
560
|
-
: { input: params };
|
|
561
|
-
return this.client.request({
|
|
562
|
-
method: "POST",
|
|
563
|
-
path: `/v1/responses/${encodeURIComponent(responseId)}/input_items`,
|
|
564
|
-
body,
|
|
565
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
566
|
-
signal: options?.signal,
|
|
567
|
-
timeoutMs: options?.timeoutMs,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
async cancel(responseId, options) {
|
|
571
|
-
return this.client.request({
|
|
572
|
-
method: "POST",
|
|
573
|
-
path: `/v1/responses/${encodeURIComponent(responseId)}/cancel`,
|
|
574
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
575
|
-
signal: options?.signal,
|
|
576
|
-
timeoutMs: options?.timeoutMs,
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
async function* streamResponses(client, ctx, params, options) {
|
|
581
|
-
const response = await requestResponseWithPayment(client, ctx, {
|
|
582
|
-
method: "POST",
|
|
583
|
-
path: "/v1/responses",
|
|
584
|
-
body: { ...cleanse(params), stream: true },
|
|
585
|
-
headers: buildCallHeaders(options, ctx.getWalletMaybe(), ctx.getTokenMaybe()),
|
|
586
|
-
signal: options?.signal,
|
|
587
|
-
timeoutMs: options?.timeoutMs,
|
|
588
|
-
expectStream: true,
|
|
589
|
-
}, options);
|
|
590
|
-
if (!response.body) {
|
|
591
|
-
throw new Error({ code: "upstream_error", message: "Streaming response had no body" });
|
|
592
|
-
}
|
|
593
|
-
let receipt = null;
|
|
594
|
-
let lastCompleted = null;
|
|
595
|
-
let streamError = null;
|
|
596
|
-
let sawDone = false;
|
|
597
|
-
let created = null;
|
|
598
|
-
let textOutput = "";
|
|
599
|
-
const outputItems = [];
|
|
600
|
-
const toolCallAggregator = new Map();
|
|
601
|
-
try {
|
|
602
|
-
for await (const frame of parseSSEStream(response.body, { signal: options?.signal })) {
|
|
603
|
-
if (frame.event === "receipt") {
|
|
604
|
-
try {
|
|
605
|
-
receipt = parseReceiptEvent(frame.data);
|
|
606
|
-
}
|
|
607
|
-
catch { /* ignore */ }
|
|
608
|
-
continue;
|
|
609
|
-
}
|
|
610
|
-
if (frame.event === "compose.error") {
|
|
611
|
-
try {
|
|
612
|
-
const parsed = JSON.parse(frame.data);
|
|
613
|
-
streamError = new Error({
|
|
614
|
-
code: parsed.code ?? "upstream_error",
|
|
615
|
-
message: parsed.message ?? "Stream error",
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
catch { /* ignore */ }
|
|
619
|
-
const decoded = decodeModelEvent(frame, { runId: options?.runId });
|
|
620
|
-
if (decoded)
|
|
621
|
-
yield decoded;
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
if (frame.data === "[DONE]") {
|
|
625
|
-
sawDone = true;
|
|
626
|
-
continue;
|
|
627
|
-
}
|
|
628
|
-
if (sawDone)
|
|
629
|
-
continue;
|
|
630
|
-
if (frame.event !== "message")
|
|
631
|
-
continue;
|
|
632
|
-
let parsed;
|
|
633
|
-
try {
|
|
634
|
-
parsed = JSON.parse(frame.data);
|
|
635
|
-
}
|
|
636
|
-
catch {
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
if (parsed.type === "response.created") {
|
|
640
|
-
created = parsed.response;
|
|
641
|
-
}
|
|
642
|
-
if (parsed.type === "response.output_text.delta") {
|
|
643
|
-
textOutput += parsed.delta;
|
|
644
|
-
}
|
|
645
|
-
if (parsed.type === "response.image_generation_call.completed") {
|
|
646
|
-
const url = `data:${parsed.mime_type || "image/png"};base64,${parsed.image_b64}`;
|
|
647
|
-
outputItems.push({
|
|
648
|
-
type: "output_image",
|
|
649
|
-
role: "assistant",
|
|
650
|
-
image_url: url,
|
|
651
|
-
mime_type: parsed.mime_type || "image/png",
|
|
652
|
-
...(parsed.revised_prompt ? { text: parsed.revised_prompt } : {}),
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
if (parsed.type === "response.output_item.completed") {
|
|
656
|
-
outputItems[parsed.output_index] = parsed.item;
|
|
657
|
-
}
|
|
658
|
-
if (parsed.type === "response.output_video.status" && parsed.status === "completed" && parsed.url) {
|
|
659
|
-
const existingIndex = outputItems.findIndex((item) => item.type === "output_video" && item.job_id === parsed.job_id);
|
|
660
|
-
const item = {
|
|
661
|
-
type: "output_video",
|
|
662
|
-
role: "assistant",
|
|
663
|
-
job_id: parsed.job_id,
|
|
664
|
-
status: parsed.status,
|
|
665
|
-
video_url: parsed.url,
|
|
666
|
-
...(typeof parsed.progress === "number" ? { progress: parsed.progress } : {}),
|
|
667
|
-
};
|
|
668
|
-
if (existingIndex >= 0)
|
|
669
|
-
outputItems[existingIndex] = { ...outputItems[existingIndex], ...item };
|
|
670
|
-
else
|
|
671
|
-
outputItems.push(item);
|
|
672
|
-
}
|
|
673
|
-
// Assemble tool_call + tool_call.delta frames so the final result
|
|
674
|
-
// carries ready-to-consume `{id,name,arguments}` entries.
|
|
675
|
-
if (parsed.type === "response.tool_call") {
|
|
676
|
-
const tc = parsed.tool_call;
|
|
677
|
-
if (tc && tc.id) {
|
|
678
|
-
const existing = toolCallAggregator.get(tc.id) ?? {
|
|
679
|
-
id: tc.id,
|
|
680
|
-
name: tc.name ?? "",
|
|
681
|
-
arguments: tc.arguments ?? "",
|
|
682
|
-
index: toolCallAggregator.size,
|
|
683
|
-
};
|
|
684
|
-
if (tc.name)
|
|
685
|
-
existing.name = tc.name;
|
|
686
|
-
if (tc.arguments)
|
|
687
|
-
existing.arguments = tc.arguments;
|
|
688
|
-
toolCallAggregator.set(tc.id, existing);
|
|
689
|
-
const walletCtx = ctx.getWalletMaybe();
|
|
690
|
-
ctx.events.emit("toolCallStart", {
|
|
691
|
-
userAddress: walletCtx.address,
|
|
692
|
-
chainId: walletCtx.chainId,
|
|
693
|
-
requestId: response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id"),
|
|
694
|
-
source: "responses",
|
|
695
|
-
toolCallId: existing.id,
|
|
696
|
-
toolName: existing.name,
|
|
697
|
-
summary: existing.arguments,
|
|
698
|
-
arguments: existing.arguments,
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
if (parsed.type === "response.tool_call.delta") {
|
|
703
|
-
const delta = parsed.delta;
|
|
704
|
-
if (delta) {
|
|
705
|
-
const idx = parsed.index;
|
|
706
|
-
const key = delta.id ?? String(idx);
|
|
707
|
-
const existing = toolCallAggregator.get(key) ?? {
|
|
708
|
-
id: delta.id ?? `call_${idx}`,
|
|
709
|
-
name: "",
|
|
710
|
-
arguments: "",
|
|
711
|
-
index: idx,
|
|
712
|
-
};
|
|
713
|
-
if (delta.id)
|
|
714
|
-
existing.id = delta.id;
|
|
715
|
-
if (delta.name)
|
|
716
|
-
existing.name += delta.name;
|
|
717
|
-
if (delta.arguments)
|
|
718
|
-
existing.arguments += delta.arguments;
|
|
719
|
-
toolCallAggregator.set(key, existing);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
if (parsed.type === "response.completed") {
|
|
723
|
-
lastCompleted = parsed;
|
|
724
|
-
// Emit toolCallEnd for every aggregated tool call — responses
|
|
725
|
-
// API doesn't have a per-tool end event, so the completion
|
|
726
|
-
// frame is the natural terminal point.
|
|
727
|
-
const walletCtx = ctx.getWalletMaybe();
|
|
728
|
-
const requestId = response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id");
|
|
729
|
-
for (const aggregated of toolCallAggregator.values()) {
|
|
730
|
-
ctx.events.emit("toolCallEnd", {
|
|
731
|
-
userAddress: walletCtx.address,
|
|
732
|
-
chainId: walletCtx.chainId,
|
|
733
|
-
requestId,
|
|
734
|
-
source: "responses",
|
|
735
|
-
toolCallId: aggregated.id,
|
|
736
|
-
toolName: aggregated.name,
|
|
737
|
-
summary: aggregated.arguments,
|
|
738
|
-
arguments: aggregated.arguments,
|
|
739
|
-
failed: false,
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
const responseId = "response_id" in parsed ? parsed.response_id : "response" in parsed ? parsed.response.id : undefined;
|
|
744
|
-
const model = "model" in parsed ? parsed.model : "response" in parsed ? parsed.response.model : undefined;
|
|
745
|
-
const decoded = decodeModelEvent(parsed, { runId: options?.runId, responseId, model });
|
|
746
|
-
if (decoded)
|
|
747
|
-
yield decoded;
|
|
748
|
-
}
|
|
749
|
-
if (streamError)
|
|
750
|
-
throw streamError;
|
|
751
|
-
const compactOutput = outputItems.filter((item) => Boolean(item));
|
|
752
|
-
if (textOutput && !compactOutput.some((item) => item.type === "output_text")) {
|
|
753
|
-
compactOutput.unshift({ type: "output_text", role: "assistant", text: textOutput });
|
|
754
|
-
}
|
|
755
|
-
const finalResponse = lastCompleted
|
|
756
|
-
? {
|
|
757
|
-
id: lastCompleted.response_id,
|
|
758
|
-
object: "response",
|
|
759
|
-
created_at: created?.created_at ?? Math.floor(Date.now() / 1000),
|
|
760
|
-
status: "completed",
|
|
761
|
-
model: lastCompleted.model,
|
|
762
|
-
output: compactOutput,
|
|
763
|
-
...(lastCompleted.usage
|
|
764
|
-
? {
|
|
765
|
-
usage: {
|
|
766
|
-
input_tokens: lastCompleted.usage.input_tokens,
|
|
767
|
-
output_tokens: lastCompleted.usage.output_tokens,
|
|
768
|
-
total_tokens: lastCompleted.usage.total_tokens,
|
|
769
|
-
},
|
|
770
|
-
}
|
|
771
|
-
: {}),
|
|
772
|
-
}
|
|
773
|
-
: created
|
|
774
|
-
? { ...created, output: compactOutput }
|
|
775
|
-
: null;
|
|
776
|
-
const requestId = response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id");
|
|
777
|
-
const { budget, sessionInvalidReason } = emitStreamingEvents(ctx, response, receipt, requestId);
|
|
778
|
-
const toolCalls = Array.from(toolCallAggregator.values())
|
|
779
|
-
.sort((a, b) => a.index - b.index)
|
|
780
|
-
.map(({ id, name, arguments: args }) => ({ id, name, arguments: args }));
|
|
781
|
-
return {
|
|
782
|
-
response: finalResponse,
|
|
783
|
-
toolCalls,
|
|
784
|
-
receipt,
|
|
785
|
-
requestId,
|
|
786
|
-
budget,
|
|
787
|
-
sessionInvalidReason,
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
finally {
|
|
791
|
-
try {
|
|
792
|
-
response.body?.cancel();
|
|
793
|
-
}
|
|
794
|
-
catch { /* best-effort */ }
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
// ---------------------------------------------------------------------------
|
|
798
|
-
// Embeddings
|
|
799
|
-
// ---------------------------------------------------------------------------
|
|
800
|
-
class EmbeddingsNamespace {
|
|
801
|
-
client;
|
|
802
|
-
ctx;
|
|
803
|
-
constructor(client, ctx) {
|
|
804
|
-
this.client = client;
|
|
805
|
-
this.ctx = ctx;
|
|
806
|
-
}
|
|
807
|
-
async create(params, options) {
|
|
808
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
809
|
-
method: "POST",
|
|
810
|
-
path: "/v1/embeddings",
|
|
811
|
-
body: cleanse(params),
|
|
812
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
813
|
-
signal: options?.signal,
|
|
814
|
-
timeoutMs: options?.timeoutMs,
|
|
815
|
-
}, options);
|
|
816
|
-
return toCompletion(this.ctx, response, data);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
// ---------------------------------------------------------------------------
|
|
820
|
-
// Images
|
|
821
|
-
// ---------------------------------------------------------------------------
|
|
822
|
-
class ImagesNamespace {
|
|
823
|
-
client;
|
|
824
|
-
ctx;
|
|
825
|
-
constructor(client, ctx) {
|
|
826
|
-
this.client = client;
|
|
827
|
-
this.ctx = ctx;
|
|
828
|
-
}
|
|
829
|
-
async generate(params, options) {
|
|
830
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
831
|
-
method: "POST",
|
|
832
|
-
path: "/v1/images/generations",
|
|
833
|
-
body: cleanse(params),
|
|
834
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
835
|
-
signal: options?.signal,
|
|
836
|
-
timeoutMs: options?.timeoutMs,
|
|
837
|
-
}, options);
|
|
838
|
-
return toCompletion(this.ctx, response, data);
|
|
839
|
-
}
|
|
840
|
-
async edit(params, options) {
|
|
841
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
842
|
-
method: "POST",
|
|
843
|
-
path: "/v1/images/edits",
|
|
844
|
-
body: cleanse(params),
|
|
845
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
846
|
-
signal: options?.signal,
|
|
847
|
-
timeoutMs: options?.timeoutMs,
|
|
848
|
-
}, options);
|
|
849
|
-
return toCompletion(this.ctx, response, data);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
// ---------------------------------------------------------------------------
|
|
853
|
-
// Audio
|
|
854
|
-
// ---------------------------------------------------------------------------
|
|
855
|
-
class AudioNamespace {
|
|
856
|
-
client;
|
|
857
|
-
ctx;
|
|
858
|
-
constructor(client, ctx) {
|
|
859
|
-
this.client = client;
|
|
860
|
-
this.ctx = ctx;
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Text-to-speech. Returns the raw audio `Response` (stream the body via
|
|
864
|
-
* `response.body` or `await response.arrayBuffer()`), plus any receipt
|
|
865
|
-
* extracted from response headers, plus the live budget snapshot.
|
|
866
|
-
*/
|
|
867
|
-
async speech(params, options) {
|
|
868
|
-
const response = await requestResponseWithPayment(this.client, this.ctx, {
|
|
869
|
-
method: "POST",
|
|
870
|
-
path: "/v1/audio/speech",
|
|
871
|
-
body: cleanse(params),
|
|
872
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
873
|
-
signal: options?.signal,
|
|
874
|
-
timeoutMs: options?.timeoutMs,
|
|
875
|
-
expectStream: true,
|
|
876
|
-
}, options);
|
|
877
|
-
// No JSON body to mirror; receipt comes from the header only.
|
|
878
|
-
const result = instrumentBillableResponse(this.ctx, response, undefined);
|
|
879
|
-
return {
|
|
880
|
-
response,
|
|
881
|
-
receipt: result.receipt,
|
|
882
|
-
requestId: result.requestId,
|
|
883
|
-
budget: result.budget,
|
|
884
|
-
sessionInvalidReason: result.sessionInvalidReason,
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Speech-to-text. The native API accepts JSON with `file` as base64 audio.
|
|
889
|
-
* Blob, File, and Uint8Array inputs are converted locally before sending.
|
|
890
|
-
*/
|
|
891
|
-
async transcriptions(params, options) {
|
|
892
|
-
const { file, provider: _provider, ...rest } = params;
|
|
893
|
-
const body = {
|
|
894
|
-
...rest,
|
|
895
|
-
file: await encodeAudioFile(file),
|
|
896
|
-
};
|
|
897
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
898
|
-
method: "POST",
|
|
899
|
-
path: "/v1/audio/transcriptions",
|
|
900
|
-
body,
|
|
901
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
902
|
-
signal: options?.signal,
|
|
903
|
-
timeoutMs: options?.timeoutMs,
|
|
904
|
-
}, options);
|
|
905
|
-
return toCompletion(this.ctx, response, data);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
// ---------------------------------------------------------------------------
|
|
909
|
-
// Videos
|
|
910
|
-
// ---------------------------------------------------------------------------
|
|
911
|
-
class VideosNamespace {
|
|
912
|
-
client;
|
|
913
|
-
ctx;
|
|
914
|
-
constructor(client, ctx) {
|
|
915
|
-
this.client = client;
|
|
916
|
-
this.ctx = ctx;
|
|
917
|
-
}
|
|
918
|
-
async generate(params, options) {
|
|
919
|
-
const { data, response } = await requestJsonWithPayment(this.client, this.ctx, {
|
|
920
|
-
method: "POST",
|
|
921
|
-
path: "/v1/videos/generations",
|
|
922
|
-
body: cleanse(params),
|
|
923
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
924
|
-
signal: options?.signal,
|
|
925
|
-
timeoutMs: options?.timeoutMs,
|
|
926
|
-
}, options);
|
|
927
|
-
return toCompletion(this.ctx, response, data);
|
|
928
|
-
}
|
|
929
|
-
get(videoId, options) {
|
|
930
|
-
return Promise.resolve(this.client.request({
|
|
931
|
-
method: "GET",
|
|
932
|
-
path: `/v1/videos/${encodeURIComponent(videoId)}`,
|
|
933
|
-
headers: buildCallHeaders(options, this.ctx.getWalletMaybe(), this.ctx.getTokenMaybe()),
|
|
934
|
-
signal: options?.signal,
|
|
935
|
-
timeoutMs: options?.timeoutMs,
|
|
936
|
-
}));
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Server-driven polling via SSE. Subscribes to `/v1/videos/:id/stream`
|
|
940
|
-
* and yields each status update as it arrives, terminating on the
|
|
941
|
-
* `completed` or `failed` state.
|
|
942
|
-
*/
|
|
943
|
-
stream(videoId, opts = {}) {
|
|
944
|
-
const iterator = streamVideoStatus(this.client, this.ctx, videoId, opts);
|
|
945
|
-
return new StreamIterator(iterator);
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Convenience helper: resolves once the video job hits a terminal state,
|
|
949
|
-
* driven by the server-side SSE poller. Aborts after `timeoutMs` elapses.
|
|
950
|
-
*/
|
|
951
|
-
async waitUntilDone(videoId, opts = {}) {
|
|
952
|
-
const stream = this.stream(videoId, opts);
|
|
953
|
-
for await (const event of stream) {
|
|
954
|
-
opts.onStatus?.(event);
|
|
955
|
-
}
|
|
956
|
-
const { final } = await stream.final();
|
|
957
|
-
return final;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
async function* streamVideoStatus(client, ctx, videoId, opts) {
|
|
961
|
-
const query = {};
|
|
962
|
-
if (opts.pollIntervalMs !== undefined)
|
|
963
|
-
query.pollIntervalMs = String(opts.pollIntervalMs);
|
|
964
|
-
if (opts.timeoutMs !== undefined)
|
|
965
|
-
query.timeoutMs = String(opts.timeoutMs);
|
|
966
|
-
const response = await client.request({
|
|
967
|
-
method: "GET",
|
|
968
|
-
path: `/v1/videos/${encodeURIComponent(videoId)}/stream`,
|
|
969
|
-
query,
|
|
970
|
-
headers: buildCallHeaders(opts, ctx.getWalletMaybe(), ctx.getTokenMaybe()),
|
|
971
|
-
signal: opts.signal,
|
|
972
|
-
timeoutMs: opts.timeoutMs,
|
|
973
|
-
expectStream: true,
|
|
974
|
-
}).asResponse();
|
|
975
|
-
if (!response.body) {
|
|
976
|
-
throw new Error({ code: "upstream_error", message: "Streaming response had no body" });
|
|
977
|
-
}
|
|
978
|
-
let lastStatus = null;
|
|
979
|
-
let emittedDone = false;
|
|
980
|
-
try {
|
|
981
|
-
for await (const frame of parseSSEStream(response.body, { signal: opts.signal })) {
|
|
982
|
-
if (frame.data === "[DONE]") {
|
|
983
|
-
if (!emittedDone) {
|
|
984
|
-
emittedDone = true;
|
|
985
|
-
}
|
|
986
|
-
continue;
|
|
987
|
-
}
|
|
988
|
-
if (emittedDone && frame.event !== "compose.error")
|
|
989
|
-
continue;
|
|
990
|
-
if (frame.event === "compose.video.status") {
|
|
991
|
-
try {
|
|
992
|
-
const parsed = JSON.parse(frame.data);
|
|
993
|
-
lastStatus = {
|
|
994
|
-
id: parsed.jobId,
|
|
995
|
-
object: "video.generation",
|
|
996
|
-
status: parsed.status,
|
|
997
|
-
url: parsed.url,
|
|
998
|
-
error: parsed.error,
|
|
999
|
-
progress: parsed.progress,
|
|
1000
|
-
};
|
|
1001
|
-
const decoded = decodeModelEvent({ type: "compose.video.status", ...parsed }, { runId: opts.runId, responseId: videoId });
|
|
1002
|
-
if (decoded)
|
|
1003
|
-
yield decoded;
|
|
1004
|
-
}
|
|
1005
|
-
catch { /* skip malformed frame */ }
|
|
1006
|
-
continue;
|
|
1007
|
-
}
|
|
1008
|
-
if (frame.event === "compose.error") {
|
|
1009
|
-
const decoded = decodeModelEvent(frame, { runId: opts.runId, responseId: videoId });
|
|
1010
|
-
if (decoded)
|
|
1011
|
-
yield decoded;
|
|
1012
|
-
continue;
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
return {
|
|
1016
|
-
final: lastStatus,
|
|
1017
|
-
requestId: response.headers.get("x-request-id") ?? response.headers.get("X-Request-Id"),
|
|
1018
|
-
};
|
|
1019
|
-
}
|
|
1020
|
-
finally {
|
|
1021
|
-
try {
|
|
1022
|
-
response.body?.cancel();
|
|
1023
|
-
}
|
|
1024
|
-
catch { /* best-effort */ }
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
// ---------------------------------------------------------------------------
|
|
1028
|
-
// Top-level inference resource
|
|
1029
|
-
// ---------------------------------------------------------------------------
|
|
1030
|
-
export class InferenceResource {
|
|
1031
|
-
chat;
|
|
1032
|
-
responses;
|
|
1033
|
-
embeddings;
|
|
1034
|
-
images;
|
|
1035
|
-
audio;
|
|
1036
|
-
videos;
|
|
1037
|
-
constructor(client, ctx) {
|
|
1038
|
-
this.chat = { completions: new ChatCompletionsNamespace(client, ctx) };
|
|
1039
|
-
this.responses = new ResponsesNamespace(client, ctx);
|
|
1040
|
-
this.embeddings = new EmbeddingsNamespace(client, ctx);
|
|
1041
|
-
this.images = new ImagesNamespace(client, ctx);
|
|
1042
|
-
this.audio = new AudioNamespace(client, ctx);
|
|
1043
|
-
this.videos = new VideosNamespace(client, ctx);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
//# sourceMappingURL=inference.js.map
|