@irogane/kaji 0.2.0-beta.11
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/LICENSE +105 -0
- package/README.md +598 -0
- package/contracts/README.md +14 -0
- package/contracts/beta-core-v1.json +52 -0
- package/contracts/cli/init-cases-v1.json +27 -0
- package/contracts/errors/error-codes.json +48 -0
- package/contracts/errors/integration-recovery-v1.json +127 -0
- package/contracts/errors/provider-normalization.json +111 -0
- package/contracts/events/conformance-invalid.json +20 -0
- package/contracts/events/conformance.json +511 -0
- package/contracts/events/new-kaji-event-v1.schema.json +1021 -0
- package/contracts/events/stored-kaji-event-v1.schema.json +1025 -0
- package/contracts/feature-tiers-v1.json +488 -0
- package/contracts/integrations/abi-index-v1.json +8 -0
- package/contracts/integrations/conformance-invalid.json +443 -0
- package/contracts/integrations/conformance-valid.json +121 -0
- package/contracts/integrations/copy-provenance-v1.schema.json +61 -0
- package/contracts/integrations/echo-tool-abi-v1.json +37 -0
- package/contracts/integrations/github-api-conformance-v1.json +644 -0
- package/contracts/integrations/github-tool-abi-typescript-v1.json +369 -0
- package/contracts/integrations/github-tool-abi-v1.json +146 -0
- package/contracts/integrations/gmail-api-conformance-v1.json +750 -0
- package/contracts/integrations/gmail-tool-abi-v1.json +62 -0
- package/contracts/integrations/index.schema.json +37 -0
- package/contracts/integrations/manifest.schema.json +119 -0
- package/contracts/parity/expected-normalized.json +4906 -0
- package/contracts/parity/scenarios.json +100 -0
- package/contracts/parity/scenarios.schema.json +214 -0
- package/contracts/providers/cost-conformance.json +112 -0
- package/contracts/release/github-proof-v1.schema.json +138 -0
- package/contracts/release/gmail-proof-v1.schema.json +138 -0
- package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +1289 -0
- package/contracts/release/publisher-identity-receipt-v1.schema.json +319 -0
- package/contracts/release/typescript-onboarding-evidence-v1.schema.json +740 -0
- package/contracts/tools/conformance-invalid.json +271 -0
- package/contracts/tools/conformance-valid.json +78 -0
- package/contracts/tools/tool-schema-v1.schema.json +18 -0
- package/dist/anthropic.cjs +1231 -0
- package/dist/anthropic.cjs.map +1 -0
- package/dist/anthropic.d.cts +26 -0
- package/dist/anthropic.d.ts +26 -0
- package/dist/anthropic.js +270 -0
- package/dist/anthropic.js.map +1 -0
- package/dist/auth.cjs +1507 -0
- package/dist/auth.cjs.map +1 -0
- package/dist/auth.d.cts +129 -0
- package/dist/auth.d.ts +129 -0
- package/dist/auth.js +1039 -0
- package/dist/auth.js.map +1 -0
- package/dist/base-B9FRMcP8.d.cts +140 -0
- package/dist/base-nHQd1VtS.d.ts +140 -0
- package/dist/chunk-AAM33KAO.js +4367 -0
- package/dist/chunk-AAM33KAO.js.map +1 -0
- package/dist/chunk-KAJ6BM64.js +153 -0
- package/dist/chunk-KAJ6BM64.js.map +1 -0
- package/dist/chunk-KCAXIOZS.js +308 -0
- package/dist/chunk-KCAXIOZS.js.map +1 -0
- package/dist/chunk-LSJ4AVO2.js +243 -0
- package/dist/chunk-LSJ4AVO2.js.map +1 -0
- package/dist/chunk-TM7ZGOJX.js +716 -0
- package/dist/chunk-TM7ZGOJX.js.map +1 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +13 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/chunk-2RCWPRVY.js +6277 -0
- package/dist/cli/chunk-2RCWPRVY.js.map +1 -0
- package/dist/cli/chunk-SEBX54TR.js +681 -0
- package/dist/cli/chunk-SEBX54TR.js.map +1 -0
- package/dist/cli/index.d.ts +232 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-worker.d.ts +2 -0
- package/dist/cli/init-worker.js +18 -0
- package/dist/cli/init-worker.js.map +1 -0
- package/dist/cli/integration-copy-worker.js +48 -0
- package/dist/cli/integration-copy-worker.js.map +1 -0
- package/dist/cli/package-entry-cjs.cjs +21 -0
- package/dist/cli/package-entry-cjs.cjs.map +1 -0
- package/dist/cli/package-entry-cjs.d.cts +2 -0
- package/dist/cli/package-entry.d.ts +2 -0
- package/dist/cli/package-entry.js +13 -0
- package/dist/cli/package-entry.js.map +1 -0
- package/dist/context-BaFHrQHv.d.cts +21 -0
- package/dist/context-BaFHrQHv.d.ts +21 -0
- package/dist/context-C-YPY-GS.d.cts +1538 -0
- package/dist/context-C-YPY-GS.d.ts +1538 -0
- package/dist/index.cjs +12346 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1560 -0
- package/dist/index.d.ts +1560 -0
- package/dist/index.js +7852 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/github.cjs +2092 -0
- package/dist/integrations/github.cjs.map +1 -0
- package/dist/integrations/github.d.cts +21 -0
- package/dist/integrations/github.d.ts +21 -0
- package/dist/integrations/github.js +2088 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations.cjs +3370 -0
- package/dist/integrations.cjs.map +1 -0
- package/dist/integrations.d.cts +202 -0
- package/dist/integrations.d.ts +202 -0
- package/dist/integrations.js +2650 -0
- package/dist/integrations.js.map +1 -0
- package/dist/observability-Cj--OkME.d.cts +96 -0
- package/dist/observability-Cj--OkME.d.ts +96 -0
- package/dist/openai.cjs +1235 -0
- package/dist/openai.cjs.map +1 -0
- package/dist/openai.d.cts +32 -0
- package/dist/openai.d.ts +32 -0
- package/dist/openai.js +272 -0
- package/dist/openai.js.map +1 -0
- package/dist/testing.cjs +554 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +43 -0
- package/dist/testing.d.ts +43 -0
- package/dist/testing.js +118 -0
- package/dist/testing.js.map +1 -0
- package/package.json +188 -0
- package/registry/echo/index.ts +53 -0
- package/registry/echo/manifest.json +53 -0
- package/registry/github/LICENSE +105 -0
- package/registry/github/client.ts +1727 -0
- package/registry/github/index.ts +263 -0
- package/registry/github/manifest.json +227 -0
- package/registry/github/owner-fixtures.json +10 -0
- package/registry/github/tests/github.test.ts +32 -0
- package/registry/gmail/LICENSE +105 -0
- package/registry/gmail/client.ts +548 -0
- package/registry/gmail/index.ts +165 -0
- package/registry/gmail/manifest.json +102 -0
- package/registry/gmail/owner-fixtures.json +10 -0
- package/registry/gmail/tests/gmail.test.ts +32 -0
- package/registry/index.json +21 -0
- package/registry/index.schema.json +37 -0
- package/registry/schema.json +119 -0
package/dist/openai.cjs
ADDED
|
@@ -0,0 +1,1235 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/events/json.ts
|
|
4
|
+
function cloneAndFreezeJson(value) {
|
|
5
|
+
return cloneJsonValue(value, /* @__PURE__ */ new WeakSet());
|
|
6
|
+
}
|
|
7
|
+
function canonicalJsonValue(value, subject = "JSON value") {
|
|
8
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
9
|
+
const assertUnicodeScalarString = (text) => {
|
|
10
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
11
|
+
const unit = text.charCodeAt(index);
|
|
12
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
13
|
+
const next = text.charCodeAt(index + 1);
|
|
14
|
+
if (!(next >= 56320 && next <= 57343)) {
|
|
15
|
+
throw new TypeError(`${subject} contains an unpaired Unicode surrogate`);
|
|
16
|
+
}
|
|
17
|
+
index += 1;
|
|
18
|
+
} else if (unit >= 56320 && unit <= 57343) {
|
|
19
|
+
throw new TypeError(`${subject} contains an unpaired Unicode surrogate`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const encode = (item) => {
|
|
24
|
+
if (item === null) return "null";
|
|
25
|
+
switch (typeof item) {
|
|
26
|
+
case "boolean":
|
|
27
|
+
return item ? "true" : "false";
|
|
28
|
+
case "string":
|
|
29
|
+
assertUnicodeScalarString(item);
|
|
30
|
+
return JSON.stringify(item);
|
|
31
|
+
case "number":
|
|
32
|
+
if (!Number.isFinite(item)) throw new TypeError(`${subject} contains a non-finite number`);
|
|
33
|
+
if (Number.isInteger(item) && !Number.isSafeInteger(item)) {
|
|
34
|
+
throw new TypeError(`${subject} contains an integer outside the I-JSON safe range`);
|
|
35
|
+
}
|
|
36
|
+
return Object.is(item, -0) ? "0" : item.toString();
|
|
37
|
+
case "object": {
|
|
38
|
+
if (ancestors.has(item)) throw new TypeError(`${subject} must be acyclic`);
|
|
39
|
+
if (Object.getOwnPropertySymbols(item).length > 0) {
|
|
40
|
+
throw new TypeError(`${subject} JSON object keys must be strings`);
|
|
41
|
+
}
|
|
42
|
+
ancestors.add(item);
|
|
43
|
+
try {
|
|
44
|
+
if (Array.isArray(item)) {
|
|
45
|
+
const descriptors = Object.getOwnPropertyDescriptors(item);
|
|
46
|
+
const values = [];
|
|
47
|
+
for (let index = 0; index < item.length; index += 1) {
|
|
48
|
+
const descriptor = descriptors[String(index)];
|
|
49
|
+
if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
|
|
50
|
+
throw new TypeError(`${subject} array values must be enumerable data values`);
|
|
51
|
+
}
|
|
52
|
+
values.push(encode(descriptor.value));
|
|
53
|
+
}
|
|
54
|
+
for (const key of Object.keys(descriptors)) {
|
|
55
|
+
if (key === "length") continue;
|
|
56
|
+
const index = Number(key);
|
|
57
|
+
if (Number.isInteger(index) && index >= 0 && index < item.length && String(index) === key) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
throw new TypeError(`${subject} arrays cannot carry named properties`);
|
|
61
|
+
}
|
|
62
|
+
return `[${values.join(",")}]`;
|
|
63
|
+
}
|
|
64
|
+
const prototype = Object.getPrototypeOf(item);
|
|
65
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
66
|
+
throw new TypeError(`${subject} contains a non-plain object`);
|
|
67
|
+
}
|
|
68
|
+
const keys = Object.getOwnPropertyNames(item);
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
assertUnicodeScalarString(key);
|
|
71
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
72
|
+
if (descriptor === void 0 || !descriptor.enumerable || !("value" in descriptor)) {
|
|
73
|
+
throw new TypeError(
|
|
74
|
+
`${subject} JSON object properties must be enumerable data values`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return `{${keys.sort().map((key) => {
|
|
79
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
80
|
+
return `${JSON.stringify(key)}:${encode(descriptor.value)}`;
|
|
81
|
+
}).join(",")}}`;
|
|
82
|
+
} finally {
|
|
83
|
+
ancestors.delete(item);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
throw new TypeError(`${subject} contains non-JSON value ${typeof item}`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return encode(value);
|
|
91
|
+
}
|
|
92
|
+
function cloneJsonValue(value, ancestors) {
|
|
93
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
94
|
+
if (typeof value === "number") {
|
|
95
|
+
if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
96
|
+
throw new TypeError("Event values must be JSON-safe");
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(value)) {
|
|
101
|
+
enter(value, ancestors);
|
|
102
|
+
const clone = value.map((item) => cloneJsonValue(item, ancestors));
|
|
103
|
+
ancestors.delete(value);
|
|
104
|
+
return Object.freeze(clone);
|
|
105
|
+
}
|
|
106
|
+
if (typeof value === "object") {
|
|
107
|
+
assertJsonObject(value);
|
|
108
|
+
enter(value, ancestors);
|
|
109
|
+
const clone = {};
|
|
110
|
+
for (const [key, child] of Object.entries(value)) {
|
|
111
|
+
Object.defineProperty(clone, key, {
|
|
112
|
+
value: cloneJsonValue(child, ancestors),
|
|
113
|
+
enumerable: true,
|
|
114
|
+
writable: true,
|
|
115
|
+
configurable: true
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
ancestors.delete(value);
|
|
119
|
+
return Object.freeze(clone);
|
|
120
|
+
}
|
|
121
|
+
throw new TypeError("Event values must be JSON-safe");
|
|
122
|
+
}
|
|
123
|
+
function assertJsonObject(value) {
|
|
124
|
+
const prototype = Object.getPrototypeOf(value);
|
|
125
|
+
if (prototype !== Object.prototype && prototype !== null || Object.getOwnPropertySymbols(value).length > 0) {
|
|
126
|
+
throw new TypeError("Event values must be JSON-safe");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function enter(value, ancestors) {
|
|
130
|
+
if (ancestors.has(value)) throw new TypeError("Event values must be JSON-safe and acyclic");
|
|
131
|
+
ancestors.add(value);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/runtime/cancellation.ts
|
|
135
|
+
var CancellationError = class extends Error {
|
|
136
|
+
constructor(message = "Agent run was cancelled") {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = "CancellationError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function throwIfCancellationRequested(token) {
|
|
142
|
+
if (!token?.isCancelled) return;
|
|
143
|
+
if (typeof token.throwIfCancelled === "function") {
|
|
144
|
+
token.throwIfCancelled();
|
|
145
|
+
}
|
|
146
|
+
throw new CancellationError();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/observability.ts
|
|
150
|
+
var METRIC_UNITS = Object.freeze({
|
|
151
|
+
"kaji.turn.queue_wait_ms": "ms",
|
|
152
|
+
"kaji.turn.duration_ms": "ms",
|
|
153
|
+
"kaji.turn.iterations": "count",
|
|
154
|
+
"kaji.provider.duration_ms": "ms",
|
|
155
|
+
"kaji.provider.retries": "count",
|
|
156
|
+
"kaji.replay.input_events": "count",
|
|
157
|
+
"kaji.context.messages": "count",
|
|
158
|
+
"kaji.context.characters": "count",
|
|
159
|
+
"kaji.tool.queue_wait_ms": "ms",
|
|
160
|
+
"kaji.tool.active": "gauge",
|
|
161
|
+
"kaji.tool.duration_ms": "ms",
|
|
162
|
+
"kaji.journal.failures": "count",
|
|
163
|
+
"kaji.subscriber.lag_events": "count",
|
|
164
|
+
"kaji.subscriber.overflow": "count",
|
|
165
|
+
"kaji.integration.auth_ms": "ms",
|
|
166
|
+
"kaji.integration.request_ms": "ms"
|
|
167
|
+
});
|
|
168
|
+
Object.freeze(
|
|
169
|
+
Object.keys(METRIC_UNITS)
|
|
170
|
+
);
|
|
171
|
+
var ALLOWED_LABELS = Object.freeze({
|
|
172
|
+
"kaji.turn.queue_wait_ms": [],
|
|
173
|
+
"kaji.turn.duration_ms": ["outcome"],
|
|
174
|
+
"kaji.turn.iterations": ["outcome"],
|
|
175
|
+
"kaji.provider.duration_ms": ["provider_family", "status"],
|
|
176
|
+
"kaji.provider.retries": ["provider_family"],
|
|
177
|
+
"kaji.replay.input_events": [],
|
|
178
|
+
"kaji.context.messages": [],
|
|
179
|
+
"kaji.context.characters": [],
|
|
180
|
+
"kaji.tool.queue_wait_ms": ["outcome"],
|
|
181
|
+
"kaji.tool.active": [],
|
|
182
|
+
"kaji.tool.duration_ms": ["outcome", "error_code"],
|
|
183
|
+
"kaji.journal.failures": ["stage"],
|
|
184
|
+
"kaji.subscriber.lag_events": [],
|
|
185
|
+
"kaji.subscriber.overflow": ["stage"],
|
|
186
|
+
"kaji.integration.auth_ms": ["integration", "operation", "outcome"],
|
|
187
|
+
"kaji.integration.request_ms": ["integration", "operation", "outcome"]
|
|
188
|
+
});
|
|
189
|
+
var STABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
190
|
+
"NONE",
|
|
191
|
+
"OTHER",
|
|
192
|
+
"INVALID_TOOL_SCHEMA",
|
|
193
|
+
"INVALID_TOOL_ARGUMENTS",
|
|
194
|
+
"UNCLASSIFIED_TOOL_RISK",
|
|
195
|
+
"MISSING_TOOL_IDENTITY",
|
|
196
|
+
"TOOL_NOT_ALLOWED",
|
|
197
|
+
"APPROVAL_UNAVAILABLE",
|
|
198
|
+
"APPROVAL_REJECTED",
|
|
199
|
+
"APPROVAL_TIMEOUT",
|
|
200
|
+
"TOOL_CANCELLED",
|
|
201
|
+
"TOOL_TIMEOUT",
|
|
202
|
+
"TURN_TIMEOUT",
|
|
203
|
+
"TOOL_EXECUTION_FAILED",
|
|
204
|
+
"TOOL_START_RECORD_FAILED",
|
|
205
|
+
"IDEMPOTENCY_CAPACITY_EXCEEDED",
|
|
206
|
+
"IDEMPOTENCY_CONFLICT",
|
|
207
|
+
"INTEGRATION_AUTH_ERROR",
|
|
208
|
+
"INTEGRATION_AUTH_REQUIRED",
|
|
209
|
+
"INTEGRATION_API_ERROR",
|
|
210
|
+
"INTEGRATION_POLICY_REJECTED",
|
|
211
|
+
"INTEGRATION_RATE_LIMITED",
|
|
212
|
+
"INTEGRATION_REDIRECT_REJECTED",
|
|
213
|
+
"INTEGRATION_RESPONSE_LIMIT"
|
|
214
|
+
]);
|
|
215
|
+
function validLabel(key, value) {
|
|
216
|
+
if (typeof value !== "string") return false;
|
|
217
|
+
const text = value;
|
|
218
|
+
if (key === "provider_family") {
|
|
219
|
+
return text === "openai" || text === "anthropic" || text === "custom";
|
|
220
|
+
}
|
|
221
|
+
if (key === "status") {
|
|
222
|
+
return text === "success" || text === "cancelled" || text === "error";
|
|
223
|
+
}
|
|
224
|
+
if (key === "stage") {
|
|
225
|
+
return ["append", "publish", "lag", "overflow"].includes(text);
|
|
226
|
+
}
|
|
227
|
+
if (key === "error_code") return STABLE_ERROR_CODES.has(text);
|
|
228
|
+
if (key === "outcome") {
|
|
229
|
+
return [
|
|
230
|
+
"acquired",
|
|
231
|
+
"completed",
|
|
232
|
+
"failed",
|
|
233
|
+
"cancelled",
|
|
234
|
+
"timeout",
|
|
235
|
+
"not_started",
|
|
236
|
+
"unknown",
|
|
237
|
+
"success",
|
|
238
|
+
"error"
|
|
239
|
+
].includes(text);
|
|
240
|
+
}
|
|
241
|
+
if (key === "integration") return text === "github" || text === "gmail";
|
|
242
|
+
if (key === "operation") return ["read", "mutation", "token"].includes(text);
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
function recordMetric(sink, name, value, labels) {
|
|
246
|
+
if (!Number.isFinite(value)) return;
|
|
247
|
+
if (sink === NOOP_METRICS) return;
|
|
248
|
+
if (!Object.hasOwn(METRIC_UNITS, name)) return;
|
|
249
|
+
const sanitized = {};
|
|
250
|
+
const allowed = ALLOWED_LABELS[name];
|
|
251
|
+
const supplied = Object.keys(labels);
|
|
252
|
+
if (supplied.length !== allowed.length || supplied.some((key) => !allowed.includes(key))) return;
|
|
253
|
+
for (const key of allowed) {
|
|
254
|
+
const raw = labels[key];
|
|
255
|
+
if (!validLabel(key, raw)) return;
|
|
256
|
+
sanitized[key] = raw;
|
|
257
|
+
}
|
|
258
|
+
const measurement = Object.freeze({
|
|
259
|
+
name,
|
|
260
|
+
value,
|
|
261
|
+
unit: METRIC_UNITS[name],
|
|
262
|
+
labels: Object.freeze(sanitized)
|
|
263
|
+
});
|
|
264
|
+
try {
|
|
265
|
+
const pending = sink.record(measurement);
|
|
266
|
+
if (pending !== void 0) void Promise.resolve(pending).catch(() => void 0);
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
var NOOP_METRICS = Object.freeze({ record: () => {
|
|
271
|
+
} });
|
|
272
|
+
|
|
273
|
+
// src/providers/base.ts
|
|
274
|
+
var PROVIDER_RESPONSE_DIAGNOSTICS = /* @__PURE__ */ Symbol.for(
|
|
275
|
+
"kaji.provider.responseDiagnostics"
|
|
276
|
+
);
|
|
277
|
+
function getProviderResponseDiagnostics(options) {
|
|
278
|
+
return options?.[PROVIDER_RESPONSE_DIAGNOSTICS];
|
|
279
|
+
}
|
|
280
|
+
var DEFAULT_PROVIDER_RESPONSE_LIMITS = Object.freeze({
|
|
281
|
+
textMaxBytes: 262144,
|
|
282
|
+
toolArgumentsMaxBytes: 65536,
|
|
283
|
+
responseMaxBytes: 524288,
|
|
284
|
+
toolCallsMax: 64
|
|
285
|
+
});
|
|
286
|
+
function resolveProviderResponseLimits(limits) {
|
|
287
|
+
const resolved = { ...DEFAULT_PROVIDER_RESPONSE_LIMITS, ...limits };
|
|
288
|
+
for (const [name, value] of Object.entries(resolved)) {
|
|
289
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
290
|
+
throw new RangeError(`${name} must be a positive safe integer`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return Object.freeze(resolved);
|
|
294
|
+
}
|
|
295
|
+
function parseRetryAfterMs(error) {
|
|
296
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
297
|
+
const headers = "headers" in error ? error.headers : "response" in error && typeof error.response === "object" ? error.response?.headers ?? null : null;
|
|
298
|
+
if (typeof headers !== "object" || headers === null) return void 0;
|
|
299
|
+
const retryAfter = headers["retry-after"];
|
|
300
|
+
if (!retryAfter) return void 0;
|
|
301
|
+
const seconds = Number(retryAfter);
|
|
302
|
+
return Number.isFinite(seconds) ? seconds * 1e3 : void 0;
|
|
303
|
+
}
|
|
304
|
+
async function withRetry(fn, retry, cancellationToken, metricsSink = NOOP_METRICS, providerFamily = "custom") {
|
|
305
|
+
const { maxAttempts, baseDelayMs } = retry;
|
|
306
|
+
let lastError;
|
|
307
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
308
|
+
throwIfCancellationRequested(cancellationToken);
|
|
309
|
+
try {
|
|
310
|
+
return await fn();
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const statusCode = typeof error === "object" && error !== null && "status" in error ? error.status : void 0;
|
|
313
|
+
if (statusCode !== 429 || attempt === maxAttempts) {
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
const retryAfterMs = parseRetryAfterMs(error) ?? baseDelayMs * 2 ** (attempt - 1);
|
|
317
|
+
recordMetric(metricsSink, "kaji.provider.retries", 1, {
|
|
318
|
+
provider_family: providerFamily
|
|
319
|
+
});
|
|
320
|
+
lastError = error;
|
|
321
|
+
await cancellableDelay(retryAfterMs, cancellationToken);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
throw lastError;
|
|
325
|
+
}
|
|
326
|
+
async function openStreamWithRetry(create, retry, cancellationToken, metricsSink = NOOP_METRICS, providerFamily = "custom") {
|
|
327
|
+
const opened = await withRetry(
|
|
328
|
+
async () => {
|
|
329
|
+
const stream = await create();
|
|
330
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
331
|
+
try {
|
|
332
|
+
return { iterator, first: await iterator.next() };
|
|
333
|
+
} catch (error) {
|
|
334
|
+
try {
|
|
335
|
+
await iterator.return?.();
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
retry,
|
|
342
|
+
cancellationToken,
|
|
343
|
+
metricsSink,
|
|
344
|
+
providerFamily
|
|
345
|
+
);
|
|
346
|
+
return new BufferedAsyncIterator(opened.iterator, opened.first);
|
|
347
|
+
}
|
|
348
|
+
var BufferedAsyncIterator = class {
|
|
349
|
+
constructor(inner, first) {
|
|
350
|
+
this.inner = inner;
|
|
351
|
+
this.first = first;
|
|
352
|
+
}
|
|
353
|
+
inner;
|
|
354
|
+
first;
|
|
355
|
+
firstPending = true;
|
|
356
|
+
closed = false;
|
|
357
|
+
closePromise;
|
|
358
|
+
async next() {
|
|
359
|
+
if (this.closed) return { value: void 0, done: true };
|
|
360
|
+
if (this.firstPending) {
|
|
361
|
+
this.firstPending = false;
|
|
362
|
+
if (this.first.done) this.markNaturallyClosed();
|
|
363
|
+
return this.first;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const next = await this.inner.next();
|
|
367
|
+
if (next.done) this.markNaturallyClosed();
|
|
368
|
+
return next;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
try {
|
|
371
|
+
await this.close();
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
throw error;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async return(value) {
|
|
378
|
+
await this.close();
|
|
379
|
+
return { value, done: true };
|
|
380
|
+
}
|
|
381
|
+
async throw(error) {
|
|
382
|
+
await this.close();
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
[Symbol.asyncIterator]() {
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
markNaturallyClosed() {
|
|
389
|
+
this.closed = true;
|
|
390
|
+
this.closePromise = Promise.resolve();
|
|
391
|
+
}
|
|
392
|
+
close() {
|
|
393
|
+
if (this.closePromise !== void 0) return this.closePromise;
|
|
394
|
+
this.closed = true;
|
|
395
|
+
this.firstPending = false;
|
|
396
|
+
try {
|
|
397
|
+
const result = this.inner.return?.();
|
|
398
|
+
this.closePromise = Promise.resolve(result).then(() => void 0);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
this.closePromise = Promise.reject(error);
|
|
401
|
+
}
|
|
402
|
+
return this.closePromise;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
async function cancellableDelay(delayMs, cancellationToken) {
|
|
406
|
+
throwIfCancellationRequested(cancellationToken);
|
|
407
|
+
if (cancellationToken === void 0) {
|
|
408
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const signal = cancellationToken?.signal;
|
|
412
|
+
if (signal === void 0) {
|
|
413
|
+
const deadline = globalThis.performance.now() + delayMs;
|
|
414
|
+
while (globalThis.performance.now() < deadline) {
|
|
415
|
+
throwIfCancellationRequested(cancellationToken);
|
|
416
|
+
await new Promise(
|
|
417
|
+
(resolve) => setTimeout(resolve, Math.min(10, deadline - globalThis.performance.now()))
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
throwIfCancellationRequested(cancellationToken);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (signal.aborted) throw new CancellationError();
|
|
424
|
+
await new Promise((resolve, reject) => {
|
|
425
|
+
const timer = setTimeout(() => {
|
|
426
|
+
signal.removeEventListener("abort", onAbort);
|
|
427
|
+
resolve();
|
|
428
|
+
}, delayMs);
|
|
429
|
+
const onAbort = () => {
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
signal.removeEventListener("abort", onAbort);
|
|
432
|
+
reject(new CancellationError());
|
|
433
|
+
};
|
|
434
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/providers/errors.ts
|
|
439
|
+
var PROVIDER_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderError.v1");
|
|
440
|
+
var PROVIDER_CONFIG_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderConfigError.v1");
|
|
441
|
+
var PROVIDER_API_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderAPIError.v1");
|
|
442
|
+
var PROVIDER_CONNECTION_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderConnectionError.v1");
|
|
443
|
+
var PROVIDER_RATE_LIMITED_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderRateLimitedError.v1");
|
|
444
|
+
var PROVIDER_OUTPUT_LIMIT_ERROR_BRAND = /* @__PURE__ */ Symbol.for("kaji.ProviderOutputLimitError.v1");
|
|
445
|
+
function brand(value, key) {
|
|
446
|
+
Object.defineProperty(value, key, { value: true });
|
|
447
|
+
}
|
|
448
|
+
function hasBrand(value, key) {
|
|
449
|
+
return typeof value === "object" && value !== null && Reflect.get(value, key) === true;
|
|
450
|
+
}
|
|
451
|
+
var ProviderError = class extends Error {
|
|
452
|
+
service;
|
|
453
|
+
action;
|
|
454
|
+
statusCode;
|
|
455
|
+
static [Symbol.hasInstance](value) {
|
|
456
|
+
return hasBrand(value, PROVIDER_ERROR_BRAND);
|
|
457
|
+
}
|
|
458
|
+
constructor(message, options = {}) {
|
|
459
|
+
super(message);
|
|
460
|
+
brand(this, PROVIDER_ERROR_BRAND);
|
|
461
|
+
this.name = new.target.name;
|
|
462
|
+
this.service = options.service ?? "provider";
|
|
463
|
+
this.action = options.action ?? "request";
|
|
464
|
+
if (options.statusCode !== void 0) this.statusCode = options.statusCode;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
var ProviderConfigError = class extends ProviderError {
|
|
468
|
+
static [Symbol.hasInstance](value) {
|
|
469
|
+
return hasBrand(value, PROVIDER_CONFIG_ERROR_BRAND);
|
|
470
|
+
}
|
|
471
|
+
constructor(message, options = {}) {
|
|
472
|
+
super(message, { ...options, action: "configure" });
|
|
473
|
+
brand(this, PROVIDER_CONFIG_ERROR_BRAND);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
var ProviderAPIError = class extends ProviderError {
|
|
477
|
+
static [Symbol.hasInstance](value) {
|
|
478
|
+
return hasBrand(value, PROVIDER_API_ERROR_BRAND);
|
|
479
|
+
}
|
|
480
|
+
constructor(message, options = {}) {
|
|
481
|
+
super(message, { ...options, action: options.action ?? "api call" });
|
|
482
|
+
brand(this, PROVIDER_API_ERROR_BRAND);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
var ProviderConnectionError = class extends ProviderError {
|
|
486
|
+
static [Symbol.hasInstance](value) {
|
|
487
|
+
return hasBrand(value, PROVIDER_CONNECTION_ERROR_BRAND);
|
|
488
|
+
}
|
|
489
|
+
constructor(message, options = {}) {
|
|
490
|
+
super(message, { ...options, action: options.action ?? "connect" });
|
|
491
|
+
brand(this, PROVIDER_CONNECTION_ERROR_BRAND);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
var ProviderRateLimitedError = class extends ProviderError {
|
|
495
|
+
retryAfterMs;
|
|
496
|
+
attempts;
|
|
497
|
+
static [Symbol.hasInstance](value) {
|
|
498
|
+
return hasBrand(value, PROVIDER_RATE_LIMITED_ERROR_BRAND);
|
|
499
|
+
}
|
|
500
|
+
constructor(message, options) {
|
|
501
|
+
super(message, { ...options, action: "api call", statusCode: 429 });
|
|
502
|
+
brand(this, PROVIDER_RATE_LIMITED_ERROR_BRAND);
|
|
503
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
504
|
+
this.attempts = options.attempts;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
var ProviderOutputLimitError = class extends Error {
|
|
508
|
+
constructor(dimension, limit) {
|
|
509
|
+
if (!["text", "tool_arguments", "total_response", "tool_calls"].includes(dimension)) {
|
|
510
|
+
throw new TypeError("unknown provider output dimension");
|
|
511
|
+
}
|
|
512
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
513
|
+
throw new RangeError("provider output limit must be a positive safe integer");
|
|
514
|
+
}
|
|
515
|
+
const unit = dimension === "tool_calls" ? "calls" : "bytes";
|
|
516
|
+
super(`Provider output exceeded ${dimension} limit of ${limit} ${unit}`);
|
|
517
|
+
this.dimension = dimension;
|
|
518
|
+
this.limit = limit;
|
|
519
|
+
brand(this, PROVIDER_OUTPUT_LIMIT_ERROR_BRAND);
|
|
520
|
+
this.name = "ProviderOutputLimitError";
|
|
521
|
+
}
|
|
522
|
+
dimension;
|
|
523
|
+
limit;
|
|
524
|
+
code = "PROVIDER_OUTPUT_LIMIT";
|
|
525
|
+
phase = "provider_stream";
|
|
526
|
+
retryable = false;
|
|
527
|
+
outcome = "unknown";
|
|
528
|
+
static [Symbol.hasInstance](value) {
|
|
529
|
+
return hasBrand(value, PROVIDER_OUTPUT_LIMIT_ERROR_BRAND);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
function errorStatusCode(error) {
|
|
533
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
534
|
+
const status = "status" in error ? error.status : void 0;
|
|
535
|
+
const statusCode = "statusCode" in error ? error.statusCode : void 0;
|
|
536
|
+
return typeof status === "number" ? status : typeof statusCode === "number" ? statusCode : void 0;
|
|
537
|
+
}
|
|
538
|
+
var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
539
|
+
"EAI_AGAIN",
|
|
540
|
+
"ECONNABORTED",
|
|
541
|
+
"ECONNREFUSED",
|
|
542
|
+
"ECONNRESET",
|
|
543
|
+
"ENETUNREACH",
|
|
544
|
+
"ENOTFOUND",
|
|
545
|
+
"ETIMEDOUT",
|
|
546
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
547
|
+
]);
|
|
548
|
+
var NETWORK_ERROR_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
549
|
+
"APIConnectionError",
|
|
550
|
+
"APIConnectionTimeoutError",
|
|
551
|
+
"APITimeoutError"
|
|
552
|
+
]);
|
|
553
|
+
function isNetworkError(error) {
|
|
554
|
+
if (typeof error !== "object" || error === null) return false;
|
|
555
|
+
const code = "code" in error ? error.code : void 0;
|
|
556
|
+
const constructorName = error.constructor?.name;
|
|
557
|
+
return typeof code === "string" && NETWORK_ERROR_CODES.has(code) || typeof constructorName === "string" && NETWORK_ERROR_TYPE_NAMES.has(constructorName);
|
|
558
|
+
}
|
|
559
|
+
function providerAPIErrorFromUnknown(service, error, action = "request") {
|
|
560
|
+
if (error instanceof ProviderConfigError) {
|
|
561
|
+
return new ProviderConfigError(`${service} configuration failed`, {
|
|
562
|
+
service,
|
|
563
|
+
statusCode: error.statusCode
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
if (error instanceof ProviderConnectionError) {
|
|
567
|
+
return new ProviderConnectionError(`${service} ${action} failed due to a network error`, {
|
|
568
|
+
service,
|
|
569
|
+
action,
|
|
570
|
+
statusCode: error.statusCode
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
if (error instanceof ProviderRateLimitedError) {
|
|
574
|
+
return new ProviderRateLimitedError(`${service} rate limit exceeded`, {
|
|
575
|
+
service,
|
|
576
|
+
retryAfterMs: error.retryAfterMs,
|
|
577
|
+
attempts: error.attempts
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
if (error instanceof ProviderError) {
|
|
581
|
+
return new ProviderAPIError(`${service} ${action} failed`, {
|
|
582
|
+
service,
|
|
583
|
+
action,
|
|
584
|
+
statusCode: error.statusCode
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
if (isNetworkError(error)) {
|
|
588
|
+
return new ProviderConnectionError(`${service} ${action} failed due to a network error`, {
|
|
589
|
+
service,
|
|
590
|
+
action
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
return new ProviderAPIError(`${service} ${action} failed`, {
|
|
594
|
+
service,
|
|
595
|
+
action,
|
|
596
|
+
statusCode: errorStatusCode(error)
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/providers/args.ts
|
|
601
|
+
function jsonTypeName(value) {
|
|
602
|
+
if (value === null) return "null";
|
|
603
|
+
if (Array.isArray(value)) return "array";
|
|
604
|
+
return typeof value;
|
|
605
|
+
}
|
|
606
|
+
function isJsonObjectRecord(value) {
|
|
607
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
608
|
+
}
|
|
609
|
+
function parseToolArgsJSON(raw, providerLabel) {
|
|
610
|
+
if (raw === void 0 || raw === null || raw === "") return {};
|
|
611
|
+
try {
|
|
612
|
+
const parsed = JSON.parse(raw);
|
|
613
|
+
if (!isJsonObjectRecord(parsed)) {
|
|
614
|
+
return {
|
|
615
|
+
__parse_error: `${providerLabel} tool args must be a JSON object, got ${jsonTypeName(parsed)}`
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
return parsed;
|
|
619
|
+
} catch (err) {
|
|
620
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
621
|
+
return { __parse_error: `${providerLabel} tool args were not valid JSON: ${msg}` };
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// src/providers/costs.ts
|
|
626
|
+
var COST_TABLE = {
|
|
627
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5 },
|
|
628
|
+
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15 },
|
|
629
|
+
"gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5 },
|
|
630
|
+
"gemini-3.5-flash": { inputPer1M: 1.5, outputPer1M: 9 }
|
|
631
|
+
};
|
|
632
|
+
var SNAPSHOT_SUFFIX = /-(?:[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{8}|[0-9]{3})$/;
|
|
633
|
+
var MAX_SAFE_TOKEN_COUNT = 9007199254740991;
|
|
634
|
+
var TOKENS_PER_RATE_UNIT = 1000000n;
|
|
635
|
+
var USD_SCALE = 10000000000n;
|
|
636
|
+
var MAX_RATE_SIGNIFICANT_DIGITS = 32;
|
|
637
|
+
var MAX_RATE_FRACTIONAL_DIGITS = 32;
|
|
638
|
+
var MAX_RATE_ABSOLUTE_EXPONENT = 32n;
|
|
639
|
+
var MAX_RATE_TEXT_LENGTH = 65;
|
|
640
|
+
var DECIMAL_RATE = /^(0|[1-9][0-9]*)(?:\.([0-9]*[1-9]))?$/;
|
|
641
|
+
var SCIENTIFIC_RATE = /^([1-9])(?:\.([0-9]*[1-9]))?e(-?[1-9][0-9]*)$/;
|
|
642
|
+
function assertTokenCount(name, value) {
|
|
643
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
|
|
644
|
+
throw new TypeError(`${name} must be an integer`);
|
|
645
|
+
}
|
|
646
|
+
if (value < 0 || value > MAX_SAFE_TOKEN_COUNT) {
|
|
647
|
+
throw new RangeError(`${name} must be between 0 and ${MAX_SAFE_TOKEN_COUNT}, inclusive`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function boundedExponent(value) {
|
|
651
|
+
const negative = value.startsWith("-");
|
|
652
|
+
const digits = value.replace(/^[+-]/, "");
|
|
653
|
+
const normalized = digits.replace(/^0+/, "") || "0";
|
|
654
|
+
if (normalized.length > 2) throw new RangeError("cost rate exponent exceeds 32");
|
|
655
|
+
const magnitude = BigInt(normalized);
|
|
656
|
+
if (magnitude > MAX_RATE_ABSOLUTE_EXPONENT) {
|
|
657
|
+
throw new RangeError("cost rate exponent exceeds 32");
|
|
658
|
+
}
|
|
659
|
+
const bounded = Number(magnitude);
|
|
660
|
+
return negative ? -bounded : bounded;
|
|
661
|
+
}
|
|
662
|
+
function numericRateText(value) {
|
|
663
|
+
if (!Number.isFinite(value)) {
|
|
664
|
+
throw new TypeError("cost rate must be a finite number or canonical decimal string");
|
|
665
|
+
}
|
|
666
|
+
if (value < 0) throw new RangeError("cost rate must be non-negative");
|
|
667
|
+
if (value === 0) return "0";
|
|
668
|
+
const [rawMantissa, exponentText] = value.toString().toLowerCase().split("e");
|
|
669
|
+
const mantissa = rawMantissa.includes(".") ? rawMantissa.replace(/0+$/, "").replace(/\.$/, "") : rawMantissa;
|
|
670
|
+
if (exponentText === void 0) return mantissa;
|
|
671
|
+
const exponent = boundedExponent(exponentText);
|
|
672
|
+
return exponent === 0 ? mantissa : `${mantissa}e${exponent}`;
|
|
673
|
+
}
|
|
674
|
+
function decimalParts(value) {
|
|
675
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
676
|
+
throw new TypeError("cost rate must be a finite number or canonical decimal string");
|
|
677
|
+
}
|
|
678
|
+
const source = typeof value === "number" ? numericRateText(value) : value;
|
|
679
|
+
if (source.length === 0 || source.length > MAX_RATE_TEXT_LENGTH) {
|
|
680
|
+
throw new RangeError("cost rate exceeds the canonical length bound");
|
|
681
|
+
}
|
|
682
|
+
let match = DECIMAL_RATE.exec(source);
|
|
683
|
+
let exponent = 0;
|
|
684
|
+
if (!match) {
|
|
685
|
+
match = SCIENTIFIC_RATE.exec(source);
|
|
686
|
+
if (!match) throw new TypeError("cost rate is not a canonical non-negative decimal");
|
|
687
|
+
exponent = boundedExponent(match[3]);
|
|
688
|
+
}
|
|
689
|
+
const fraction = match[2] ?? "";
|
|
690
|
+
const significant = `${match[1]}${fraction}`.replace(/^0+/, "") || "0";
|
|
691
|
+
if (significant.length > MAX_RATE_SIGNIFICANT_DIGITS) {
|
|
692
|
+
throw new RangeError("cost rate exceeds 32 significant digits");
|
|
693
|
+
}
|
|
694
|
+
if (fraction.length > MAX_RATE_FRACTIONAL_DIGITS) {
|
|
695
|
+
throw new RangeError("cost rate exceeds 32 fractional digits");
|
|
696
|
+
}
|
|
697
|
+
let coefficient = BigInt(`${match[1]}${fraction}`);
|
|
698
|
+
let scale = fraction.length - exponent;
|
|
699
|
+
if (scale < 0) {
|
|
700
|
+
coefficient *= 10n ** BigInt(-scale);
|
|
701
|
+
scale = 0;
|
|
702
|
+
}
|
|
703
|
+
return { coefficient, scale };
|
|
704
|
+
}
|
|
705
|
+
function canonicalUsd(units) {
|
|
706
|
+
const whole = units / USD_SCALE;
|
|
707
|
+
const fraction = (units % USD_SCALE).toString().padStart(10, "0").replace(/0+$/, "");
|
|
708
|
+
return fraction ? `${whole}.${fraction}` : whole.toString();
|
|
709
|
+
}
|
|
710
|
+
function calculateCostFromRatesUsdCanonical(inputTokens, outputTokens, inputPer1M, outputPer1M) {
|
|
711
|
+
assertTokenCount("inputTokens", inputTokens);
|
|
712
|
+
assertTokenCount("outputTokens", outputTokens);
|
|
713
|
+
const input = decimalParts(inputPer1M);
|
|
714
|
+
const output = decimalParts(outputPer1M);
|
|
715
|
+
const rateScale = Math.max(input.scale, output.scale);
|
|
716
|
+
const numerator = BigInt(inputTokens) * input.coefficient * 10n ** BigInt(rateScale - input.scale) + BigInt(outputTokens) * output.coefficient * 10n ** BigInt(rateScale - output.scale);
|
|
717
|
+
const denominator = TOKENS_PER_RATE_UNIT * 10n ** BigInt(rateScale);
|
|
718
|
+
const scaled = numerator * USD_SCALE;
|
|
719
|
+
let units = scaled / denominator;
|
|
720
|
+
const remainder = scaled % denominator;
|
|
721
|
+
const comparison = remainder * 2n - denominator;
|
|
722
|
+
if (comparison > 0n || comparison === 0n && units % 2n === 1n) units += 1n;
|
|
723
|
+
return canonicalUsd(units);
|
|
724
|
+
}
|
|
725
|
+
function lookupCost(model) {
|
|
726
|
+
if (model in COST_TABLE) return COST_TABLE[model];
|
|
727
|
+
const base = model.replace(SNAPSHOT_SUFFIX, "");
|
|
728
|
+
return base === model ? void 0 : COST_TABLE[base];
|
|
729
|
+
}
|
|
730
|
+
function calculateCostUsdCanonical(model, inputTokens, outputTokens) {
|
|
731
|
+
assertTokenCount("inputTokens", inputTokens);
|
|
732
|
+
assertTokenCount("outputTokens", outputTokens);
|
|
733
|
+
const entry = lookupCost(model);
|
|
734
|
+
if (!entry) return "0";
|
|
735
|
+
return calculateCostFromRatesUsdCanonical(
|
|
736
|
+
inputTokens,
|
|
737
|
+
outputTokens,
|
|
738
|
+
entry.inputPer1M,
|
|
739
|
+
entry.outputPer1M
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
function calculateCostUsd(model, inputTokens, outputTokens) {
|
|
743
|
+
return Number(calculateCostUsdCanonical(model, inputTokens, outputTokens));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// src/providers/openai-format.ts
|
|
747
|
+
function toOpenAIChatMessages(messages) {
|
|
748
|
+
return messages.map((m) => {
|
|
749
|
+
if (m.role === "tool") {
|
|
750
|
+
return {
|
|
751
|
+
role: "tool",
|
|
752
|
+
content: m.content,
|
|
753
|
+
tool_call_id: m.tool_call_id ?? ""
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
757
|
+
return {
|
|
758
|
+
role: "assistant",
|
|
759
|
+
content: m.content,
|
|
760
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
761
|
+
id: tc.id,
|
|
762
|
+
type: "function",
|
|
763
|
+
function: {
|
|
764
|
+
name: tc.name,
|
|
765
|
+
arguments: JSON.stringify(tc.args ?? {})
|
|
766
|
+
}
|
|
767
|
+
}))
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
return { role: m.role, content: m.content };
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// src/providers/response-budget.ts
|
|
775
|
+
function scanUtf8Scalars(fragment, pendingHighSurrogate) {
|
|
776
|
+
if (typeof fragment !== "string")
|
|
777
|
+
throw new TypeError("provider output fragments must be strings");
|
|
778
|
+
const input = `${pendingHighSurrogate ?? ""}${fragment}`;
|
|
779
|
+
const scalars = [];
|
|
780
|
+
let bytes = 0;
|
|
781
|
+
let pending;
|
|
782
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
783
|
+
const unit = input.charCodeAt(index);
|
|
784
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
785
|
+
if (index + 1 >= input.length) {
|
|
786
|
+
pending = input[index];
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
const low = input.charCodeAt(index + 1);
|
|
790
|
+
if (!(low >= 56320 && low <= 57343)) {
|
|
791
|
+
throw new TypeError("provider output contains an unpaired Unicode surrogate");
|
|
792
|
+
}
|
|
793
|
+
scalars.push({ text: input.slice(index, index + 2), bytes: 4 });
|
|
794
|
+
bytes += 4;
|
|
795
|
+
index += 1;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (unit >= 56320 && unit <= 57343) {
|
|
799
|
+
throw new TypeError("provider output contains an unpaired Unicode surrogate");
|
|
800
|
+
}
|
|
801
|
+
const scalarBytes = unit <= 127 ? 1 : unit <= 2047 ? 2 : 3;
|
|
802
|
+
scalars.push({ text: input[index], bytes: scalarBytes });
|
|
803
|
+
bytes += scalarBytes;
|
|
804
|
+
}
|
|
805
|
+
return pending === void 0 ? { scalars, bytes } : { scalars, bytes, pendingHighSurrogate: pending };
|
|
806
|
+
}
|
|
807
|
+
function utf8ByteLength(value) {
|
|
808
|
+
const scan = scanUtf8Scalars(value);
|
|
809
|
+
if (scan.pendingHighSurrogate !== void 0) {
|
|
810
|
+
throw new TypeError("provider output contains an unpaired Unicode surrogate");
|
|
811
|
+
}
|
|
812
|
+
return scan.bytes;
|
|
813
|
+
}
|
|
814
|
+
var LinearStringParts = class {
|
|
815
|
+
parts = [];
|
|
816
|
+
_fragmentCount = 0;
|
|
817
|
+
_joinOperations = 0;
|
|
818
|
+
get fragmentCount() {
|
|
819
|
+
return this._fragmentCount;
|
|
820
|
+
}
|
|
821
|
+
get joinOperations() {
|
|
822
|
+
return this._joinOperations;
|
|
823
|
+
}
|
|
824
|
+
append(fragment) {
|
|
825
|
+
if (!fragment) return;
|
|
826
|
+
this.parts.push(fragment);
|
|
827
|
+
this._fragmentCount += 1;
|
|
828
|
+
}
|
|
829
|
+
join() {
|
|
830
|
+
this._joinOperations += 1;
|
|
831
|
+
return this.parts.join("");
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
var ProviderResponseBudget = class {
|
|
835
|
+
limits;
|
|
836
|
+
textBytes = 0;
|
|
837
|
+
totalBytes = 0;
|
|
838
|
+
callCount = 0;
|
|
839
|
+
rawFragments = 0;
|
|
840
|
+
argumentJoins = 0;
|
|
841
|
+
textPendingHighSurrogate;
|
|
842
|
+
rawToolCalls = /* @__PURE__ */ new Map();
|
|
843
|
+
constructor(limits) {
|
|
844
|
+
this.limits = resolveProviderResponseLimits(limits);
|
|
845
|
+
}
|
|
846
|
+
get diagnostics() {
|
|
847
|
+
return Object.freeze({
|
|
848
|
+
textBytes: this.textBytes,
|
|
849
|
+
totalResponseBytes: this.totalBytes,
|
|
850
|
+
toolCalls: this.callCount,
|
|
851
|
+
rawFragments: this.rawFragments,
|
|
852
|
+
toolArgumentJoinOperations: this.argumentJoins
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
get providerDiagnostics() {
|
|
856
|
+
return Object.freeze({
|
|
857
|
+
rawFragments: this.rawFragments,
|
|
858
|
+
toolArgumentJoinOperations: this.argumentJoins
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
acceptRaw(input) {
|
|
862
|
+
const text = input.text ?? "";
|
|
863
|
+
const textScan = scanUtf8Scalars(text, this.textPendingHighSurrogate);
|
|
864
|
+
const nextTextBytes = this.textBytes + textScan.bytes;
|
|
865
|
+
if (nextTextBytes > this.limits.textMaxBytes) {
|
|
866
|
+
throw new ProviderOutputLimitError("text", this.limits.textMaxBytes);
|
|
867
|
+
}
|
|
868
|
+
const nextStates = new Map([...this.rawToolCalls].map(([key, state]) => [key, { ...state }]));
|
|
869
|
+
let addedTotal = textScan.bytes;
|
|
870
|
+
let addedCalls = 0;
|
|
871
|
+
let addedFragments = text ? 1 : 0;
|
|
872
|
+
for (const fragment of input.toolFragments ?? []) {
|
|
873
|
+
let state = nextStates.get(fragment.key);
|
|
874
|
+
if (fragment.startsCall) {
|
|
875
|
+
if (state !== void 0) throw new TypeError("provider tool call started more than once");
|
|
876
|
+
state = { argumentBytes: 0 };
|
|
877
|
+
nextStates.set(fragment.key, state);
|
|
878
|
+
addedCalls += 1;
|
|
879
|
+
} else if (state === void 0) {
|
|
880
|
+
throw new TypeError("provider tool fragment must start a call before appending");
|
|
881
|
+
}
|
|
882
|
+
const id = scanUtf8Scalars(fragment.idFragment ?? "", state.idPending);
|
|
883
|
+
const name = scanUtf8Scalars(fragment.nameFragment ?? "", state.namePending);
|
|
884
|
+
const args = scanUtf8Scalars(fragment.argumentsFragment ?? "", state.argumentsPending);
|
|
885
|
+
state.idPending = id.pendingHighSurrogate;
|
|
886
|
+
state.namePending = name.pendingHighSurrogate;
|
|
887
|
+
state.argumentsPending = args.pendingHighSurrogate;
|
|
888
|
+
state.argumentBytes += args.bytes;
|
|
889
|
+
if (state.argumentBytes > this.limits.toolArgumentsMaxBytes) {
|
|
890
|
+
throw new ProviderOutputLimitError("tool_arguments", this.limits.toolArgumentsMaxBytes);
|
|
891
|
+
}
|
|
892
|
+
addedTotal += id.bytes + name.bytes + args.bytes;
|
|
893
|
+
addedFragments += Number(Boolean(fragment.idFragment));
|
|
894
|
+
addedFragments += Number(Boolean(fragment.nameFragment));
|
|
895
|
+
addedFragments += Number(Boolean(fragment.argumentsFragment));
|
|
896
|
+
}
|
|
897
|
+
const nextCalls = this.callCount + addedCalls;
|
|
898
|
+
if (nextCalls > this.limits.toolCallsMax) {
|
|
899
|
+
throw new ProviderOutputLimitError("tool_calls", this.limits.toolCallsMax);
|
|
900
|
+
}
|
|
901
|
+
const nextTotal = this.totalBytes + addedTotal;
|
|
902
|
+
if (nextTotal > this.limits.responseMaxBytes) {
|
|
903
|
+
throw new ProviderOutputLimitError("total_response", this.limits.responseMaxBytes);
|
|
904
|
+
}
|
|
905
|
+
this.textBytes = nextTextBytes;
|
|
906
|
+
this.totalBytes = nextTotal;
|
|
907
|
+
this.callCount = nextCalls;
|
|
908
|
+
this.rawFragments += addedFragments;
|
|
909
|
+
this.textPendingHighSurrogate = textScan.pendingHighSurrogate;
|
|
910
|
+
this.rawToolCalls = nextStates;
|
|
911
|
+
}
|
|
912
|
+
acceptNormalized(delta, toolCalls) {
|
|
913
|
+
const textScan = scanUtf8Scalars(delta, this.textPendingHighSurrogate);
|
|
914
|
+
const nextTextBytes = this.textBytes + textScan.bytes;
|
|
915
|
+
if (nextTextBytes > this.limits.textMaxBytes) {
|
|
916
|
+
throw new ProviderOutputLimitError("text", this.limits.textMaxBytes);
|
|
917
|
+
}
|
|
918
|
+
let addedTotal = textScan.bytes;
|
|
919
|
+
const serializedCalls = [];
|
|
920
|
+
for (const call of toolCalls) {
|
|
921
|
+
const descriptors = Object.getOwnPropertyDescriptors(call);
|
|
922
|
+
const id = dataProperty(descriptors, "id");
|
|
923
|
+
const name = dataProperty(descriptors, "name");
|
|
924
|
+
const args = dataProperty(descriptors, "args");
|
|
925
|
+
if (typeof id !== "string" || typeof name !== "string") {
|
|
926
|
+
throw new TypeError("provider tool call id and name must be strings");
|
|
927
|
+
}
|
|
928
|
+
const encodedArguments = canonicalJsonValue(args ?? {}, "tool arguments");
|
|
929
|
+
const argumentBytes = utf8ByteLength(encodedArguments);
|
|
930
|
+
if (argumentBytes > this.limits.toolArgumentsMaxBytes) {
|
|
931
|
+
throw new ProviderOutputLimitError("tool_arguments", this.limits.toolArgumentsMaxBytes);
|
|
932
|
+
}
|
|
933
|
+
addedTotal += utf8ByteLength(id) + utf8ByteLength(name) + argumentBytes;
|
|
934
|
+
serializedCalls.push({ id, name, encodedArguments });
|
|
935
|
+
}
|
|
936
|
+
const nextCalls = this.callCount + serializedCalls.length;
|
|
937
|
+
if (nextCalls > this.limits.toolCallsMax) {
|
|
938
|
+
throw new ProviderOutputLimitError("tool_calls", this.limits.toolCallsMax);
|
|
939
|
+
}
|
|
940
|
+
const nextTotal = this.totalBytes + addedTotal;
|
|
941
|
+
if (nextTotal > this.limits.responseMaxBytes) {
|
|
942
|
+
throw new ProviderOutputLimitError("total_response", this.limits.responseMaxBytes);
|
|
943
|
+
}
|
|
944
|
+
const detached = serializedCalls.map(
|
|
945
|
+
({ id, name, encodedArguments }) => Object.freeze({
|
|
946
|
+
id,
|
|
947
|
+
name,
|
|
948
|
+
args: cloneAndFreezeJson(JSON.parse(encodedArguments))
|
|
949
|
+
})
|
|
950
|
+
);
|
|
951
|
+
this.textBytes = nextTextBytes;
|
|
952
|
+
this.totalBytes = nextTotal;
|
|
953
|
+
this.callCount = nextCalls;
|
|
954
|
+
this.textPendingHighSurrogate = textScan.pendingHighSurrogate;
|
|
955
|
+
return Object.freeze({ delta, toolCalls: Object.freeze(detached) });
|
|
956
|
+
}
|
|
957
|
+
finishRawTool(key) {
|
|
958
|
+
const state = this.rawToolCalls.get(key);
|
|
959
|
+
if (state === void 0) throw new TypeError("unknown provider tool call");
|
|
960
|
+
assertNoPendingSurrogate(state.idPending);
|
|
961
|
+
assertNoPendingSurrogate(state.namePending);
|
|
962
|
+
assertNoPendingSurrogate(state.argumentsPending);
|
|
963
|
+
}
|
|
964
|
+
finish() {
|
|
965
|
+
assertNoPendingSurrogate(this.textPendingHighSurrogate);
|
|
966
|
+
for (const key of this.rawToolCalls.keys()) this.finishRawTool(key);
|
|
967
|
+
}
|
|
968
|
+
recordToolArgumentJoin() {
|
|
969
|
+
this.argumentJoins += 1;
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
function dataProperty(descriptors, key) {
|
|
973
|
+
const descriptor = descriptors[key];
|
|
974
|
+
if (descriptor === void 0) return void 0;
|
|
975
|
+
if (!descriptor.enumerable || !("value" in descriptor)) {
|
|
976
|
+
throw new TypeError("provider tool calls must contain enumerable data properties");
|
|
977
|
+
}
|
|
978
|
+
return descriptor.value;
|
|
979
|
+
}
|
|
980
|
+
function assertNoPendingSurrogate(pending) {
|
|
981
|
+
if (pending !== void 0) {
|
|
982
|
+
throw new TypeError("provider output contains an unpaired Unicode surrogate");
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
async function closeProviderStream(stream) {
|
|
986
|
+
if (typeof stream !== "object" || stream === null) return;
|
|
987
|
+
const candidate = stream;
|
|
988
|
+
const close = candidate.controller?.abort?.bind(candidate.controller) ?? candidate.abort?.bind(candidate) ?? candidate.return?.bind(candidate) ?? candidate.close?.bind(candidate);
|
|
989
|
+
if (close === void 0) return;
|
|
990
|
+
try {
|
|
991
|
+
await close();
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// src/providers/openai.ts
|
|
997
|
+
function toOpenAITools(tools) {
|
|
998
|
+
return tools.map((t) => ({
|
|
999
|
+
type: "function",
|
|
1000
|
+
function: {
|
|
1001
|
+
name: t.name,
|
|
1002
|
+
description: t.description,
|
|
1003
|
+
parameters: t.parameters
|
|
1004
|
+
}
|
|
1005
|
+
}));
|
|
1006
|
+
}
|
|
1007
|
+
function parseToolCalls(raw) {
|
|
1008
|
+
return (raw ?? []).map((tc) => {
|
|
1009
|
+
if (tc.type !== "function") {
|
|
1010
|
+
return { id: tc.id ?? "", name: "", args: {} };
|
|
1011
|
+
}
|
|
1012
|
+
const argsRaw = tc.function?.arguments;
|
|
1013
|
+
return {
|
|
1014
|
+
id: tc.id ?? "",
|
|
1015
|
+
name: tc.function?.name ?? "",
|
|
1016
|
+
// The OpenAI SDK types arguments as `string`; treat non-strings as
|
|
1017
|
+
// already-parsed for forward-compat with mocked SDK shapes.
|
|
1018
|
+
args: typeof argsRaw === "string" ? parseToolArgsJSON(argsRaw, "OpenAI") : argsRaw ?? {}
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
function rawToolCallFragments(raw) {
|
|
1023
|
+
return (raw ?? []).map((toolCall, key) => {
|
|
1024
|
+
const args = toolCall.type === "function" ? toolCall.function?.arguments : void 0;
|
|
1025
|
+
return {
|
|
1026
|
+
key,
|
|
1027
|
+
startsCall: true,
|
|
1028
|
+
idFragment: toolCall.id ?? "",
|
|
1029
|
+
nameFragment: toolCall.type === "function" ? toolCall.function?.name ?? "" : "",
|
|
1030
|
+
argumentsFragment: typeof args === "string" ? args : canonicalJsonValue(args ?? {}, "tool arguments")
|
|
1031
|
+
};
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
var OpenAIProvider = class {
|
|
1035
|
+
providerFamily = "openai";
|
|
1036
|
+
opts;
|
|
1037
|
+
client = null;
|
|
1038
|
+
constructor(opts) {
|
|
1039
|
+
if (!opts.apiKey?.trim()) {
|
|
1040
|
+
throw new ProviderConfigError("OpenAI API key is not configured.", { service: "openai" });
|
|
1041
|
+
}
|
|
1042
|
+
if (opts.requestTimeoutMs !== void 0 && (!Number.isFinite(opts.requestTimeoutMs) || !Number.isInteger(opts.requestTimeoutMs) || opts.requestTimeoutMs <= 0)) {
|
|
1043
|
+
throw new RangeError("requestTimeoutMs must be a positive finite integer");
|
|
1044
|
+
}
|
|
1045
|
+
this.opts = {
|
|
1046
|
+
apiKey: opts.apiKey,
|
|
1047
|
+
model: opts.model ?? "gpt-5.4-mini",
|
|
1048
|
+
baseURL: opts.baseURL ?? "",
|
|
1049
|
+
temperature: opts.temperature ?? 0.7,
|
|
1050
|
+
maxTokens: opts.maxTokens ?? 4096,
|
|
1051
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
1052
|
+
defaultHeaders: opts.defaultHeaders && Object.keys(opts.defaultHeaders).length > 0 ? opts.defaultHeaders : void 0,
|
|
1053
|
+
retry: {
|
|
1054
|
+
maxAttempts: opts.retry?.maxAttempts ?? 3,
|
|
1055
|
+
baseDelayMs: opts.retry?.baseDelayMs ?? 1e3
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
/** Expose the model name for downstream cost calculation. */
|
|
1060
|
+
get model() {
|
|
1061
|
+
return this.opts.model;
|
|
1062
|
+
}
|
|
1063
|
+
async createClient() {
|
|
1064
|
+
const { default: OpenAIDefault } = await import('openai');
|
|
1065
|
+
return new OpenAIDefault({
|
|
1066
|
+
apiKey: this.opts.apiKey,
|
|
1067
|
+
maxRetries: 0,
|
|
1068
|
+
...this.opts.baseURL ? { baseURL: this.opts.baseURL } : {},
|
|
1069
|
+
...this.opts.defaultHeaders ? { defaultHeaders: this.opts.defaultHeaders } : {}
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
async getClient() {
|
|
1073
|
+
if (this.client !== null) return this.client;
|
|
1074
|
+
try {
|
|
1075
|
+
this.client = await this.createClient();
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
if (error instanceof ProviderError) throw error;
|
|
1078
|
+
throw new ProviderConfigError("OpenAI provider requires the openai package.", {
|
|
1079
|
+
service: "openai"
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
return this.client;
|
|
1083
|
+
}
|
|
1084
|
+
async generate(messages, tools, options) {
|
|
1085
|
+
throwIfCancellationRequested(options?.cancellationToken);
|
|
1086
|
+
const client = await this.getClient();
|
|
1087
|
+
const params = {
|
|
1088
|
+
model: this.opts.model,
|
|
1089
|
+
messages: toOpenAIChatMessages(messages),
|
|
1090
|
+
temperature: options?.temperature ?? this.opts.temperature,
|
|
1091
|
+
max_completion_tokens: options?.maxTokens ?? this.opts.maxTokens
|
|
1092
|
+
};
|
|
1093
|
+
if (tools.length > 0) params.tools = toOpenAITools(tools);
|
|
1094
|
+
const budget = new ProviderResponseBudget(options?.responseLimits);
|
|
1095
|
+
try {
|
|
1096
|
+
const response = await withRetry(
|
|
1097
|
+
() => client.chat.completions.create(params, {
|
|
1098
|
+
signal: options?.cancellationToken?.signal,
|
|
1099
|
+
...this.opts.requestTimeoutMs === void 0 ? {} : { timeout: this.opts.requestTimeoutMs }
|
|
1100
|
+
}),
|
|
1101
|
+
this.opts.retry,
|
|
1102
|
+
options?.cancellationToken,
|
|
1103
|
+
options?.metricsSink,
|
|
1104
|
+
"openai"
|
|
1105
|
+
);
|
|
1106
|
+
const choice = response.choices[0];
|
|
1107
|
+
if (!choice) {
|
|
1108
|
+
return { content: "", toolCalls: [], usage: void 0 };
|
|
1109
|
+
}
|
|
1110
|
+
const message = choice.message;
|
|
1111
|
+
const content = message.content ?? "";
|
|
1112
|
+
budget.acceptRaw({
|
|
1113
|
+
text: content,
|
|
1114
|
+
toolFragments: rawToolCallFragments(message.tool_calls)
|
|
1115
|
+
});
|
|
1116
|
+
budget.finish();
|
|
1117
|
+
const usage = response.usage ? { input: response.usage.prompt_tokens, output: response.usage.completion_tokens } : void 0;
|
|
1118
|
+
const costUsd = usage && lookupCost(this.opts.model) !== void 0 ? calculateCostUsd(this.opts.model, usage.input, usage.output) : void 0;
|
|
1119
|
+
return {
|
|
1120
|
+
content,
|
|
1121
|
+
toolCalls: parseToolCalls(message.tool_calls),
|
|
1122
|
+
usage,
|
|
1123
|
+
costUsd
|
|
1124
|
+
};
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
if (error instanceof ProviderOutputLimitError) throw error;
|
|
1127
|
+
throwIfCancellationRequested(options?.cancellationToken);
|
|
1128
|
+
throw providerAPIErrorFromUnknown("openai", error, "request");
|
|
1129
|
+
} finally {
|
|
1130
|
+
getProviderResponseDiagnostics(options)?.record(budget.providerDiagnostics);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async *generateStream(messages, tools, options) {
|
|
1134
|
+
throwIfCancellationRequested(options?.cancellationToken);
|
|
1135
|
+
const client = await this.getClient();
|
|
1136
|
+
const params = {
|
|
1137
|
+
model: this.opts.model,
|
|
1138
|
+
messages: toOpenAIChatMessages(messages),
|
|
1139
|
+
temperature: options?.temperature ?? this.opts.temperature,
|
|
1140
|
+
max_completion_tokens: options?.maxTokens ?? this.opts.maxTokens,
|
|
1141
|
+
stream: true,
|
|
1142
|
+
stream_options: { include_usage: true }
|
|
1143
|
+
};
|
|
1144
|
+
if (tools.length > 0) params.tools = toOpenAITools(tools);
|
|
1145
|
+
const budget = new ProviderResponseBudget(options?.responseLimits);
|
|
1146
|
+
let stream;
|
|
1147
|
+
try {
|
|
1148
|
+
stream = await openStreamWithRetry(
|
|
1149
|
+
() => client.chat.completions.create(params, {
|
|
1150
|
+
signal: options?.cancellationToken?.signal,
|
|
1151
|
+
...this.opts.requestTimeoutMs === void 0 ? {} : { timeout: this.opts.requestTimeoutMs }
|
|
1152
|
+
}),
|
|
1153
|
+
this.opts.retry,
|
|
1154
|
+
options?.cancellationToken,
|
|
1155
|
+
options?.metricsSink,
|
|
1156
|
+
"openai"
|
|
1157
|
+
);
|
|
1158
|
+
const pendingCalls = /* @__PURE__ */ new Map();
|
|
1159
|
+
for await (const chunk of stream) {
|
|
1160
|
+
const usage = chunk.usage ? { input: chunk.usage.prompt_tokens, output: chunk.usage.completion_tokens } : void 0;
|
|
1161
|
+
if (usage) {
|
|
1162
|
+
yield {
|
|
1163
|
+
delta: "",
|
|
1164
|
+
toolCalls: [],
|
|
1165
|
+
usage,
|
|
1166
|
+
costUsd: lookupCost(this.opts.model) === void 0 ? void 0 : calculateCostUsd(this.opts.model, usage.input, usage.output)
|
|
1167
|
+
};
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const delta = chunk.choices[0]?.delta;
|
|
1171
|
+
if (!delta) continue;
|
|
1172
|
+
const text = delta.content ?? "";
|
|
1173
|
+
const incomingCalls = [];
|
|
1174
|
+
const fragments = [];
|
|
1175
|
+
const knownIndices = new Set(pendingCalls.keys());
|
|
1176
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
1177
|
+
const idx = tc.index ?? 0;
|
|
1178
|
+
const startsCall = !knownIndices.has(idx);
|
|
1179
|
+
knownIndices.add(idx);
|
|
1180
|
+
fragments.push({
|
|
1181
|
+
key: idx,
|
|
1182
|
+
startsCall,
|
|
1183
|
+
idFragment: tc.id ?? "",
|
|
1184
|
+
nameFragment: tc.function?.name ?? "",
|
|
1185
|
+
argumentsFragment: tc.function?.arguments ?? ""
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
budget.acceptRaw({ text, toolFragments: fragments });
|
|
1189
|
+
for (const fragment of fragments) {
|
|
1190
|
+
let entry = pendingCalls.get(fragment.key);
|
|
1191
|
+
if (entry === void 0) {
|
|
1192
|
+
entry = {
|
|
1193
|
+
id: new LinearStringParts(),
|
|
1194
|
+
name: new LinearStringParts(),
|
|
1195
|
+
arguments: new LinearStringParts()
|
|
1196
|
+
};
|
|
1197
|
+
pendingCalls.set(fragment.key, entry);
|
|
1198
|
+
}
|
|
1199
|
+
entry.id.append(fragment.idFragment ?? "");
|
|
1200
|
+
entry.name.append(fragment.nameFragment ?? "");
|
|
1201
|
+
entry.arguments.append(fragment.argumentsFragment ?? "");
|
|
1202
|
+
}
|
|
1203
|
+
if (chunk.choices[0]?.finish_reason === "tool_calls") {
|
|
1204
|
+
for (const [key, entry] of pendingCalls) {
|
|
1205
|
+
budget.finishRawTool(key);
|
|
1206
|
+
budget.recordToolArgumentJoin();
|
|
1207
|
+
incomingCalls.push({
|
|
1208
|
+
id: entry.id.join(),
|
|
1209
|
+
name: entry.name.join(),
|
|
1210
|
+
args: parseToolArgsJSON(entry.arguments.join(), "OpenAI")
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
pendingCalls.clear();
|
|
1214
|
+
}
|
|
1215
|
+
if (text || incomingCalls.length > 0) {
|
|
1216
|
+
yield { delta: text, toolCalls: incomingCalls };
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
budget.finish();
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
if (error instanceof ProviderOutputLimitError) {
|
|
1222
|
+
await closeProviderStream(stream);
|
|
1223
|
+
throw error;
|
|
1224
|
+
}
|
|
1225
|
+
throwIfCancellationRequested(options?.cancellationToken);
|
|
1226
|
+
throw providerAPIErrorFromUnknown("openai", error, "stream");
|
|
1227
|
+
} finally {
|
|
1228
|
+
getProviderResponseDiagnostics(options)?.record(budget.providerDiagnostics);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
exports.OpenAIProvider = OpenAIProvider;
|
|
1234
|
+
//# sourceMappingURL=openai.cjs.map
|
|
1235
|
+
//# sourceMappingURL=openai.cjs.map
|