@theokit/sdk 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cron-D_0p_gXd.d.ts → cron-BF_AI5NP.d.ts} +31 -0
- package/dist/{cron-26WH1n7o.d.cts → cron-DXEhagMa.d.cts} +31 -0
- package/dist/cron.cjs +184 -169
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +1 -1
- package/dist/cron.d.ts +1 -1
- package/dist/cron.js +184 -169
- package/dist/cron.js.map +1 -1
- package/dist/eval.cjs +184 -169
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +184 -169
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +184 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +184 -169
- package/dist/index.js.map +1 -1
- package/dist/types/provider-profile.d.ts +31 -0
- package/package.json +1 -1
|
@@ -1185,6 +1185,32 @@ interface MemoryProvider {
|
|
|
1185
1185
|
*/
|
|
1186
1186
|
type ApiMode = "chat_completions" | "anthropic_messages" | "responses_api" | "bedrock" | "bedrock_anthropic";
|
|
1187
1187
|
type AuthType = "api_key" | "oauth_device_code" | "oauth_external" | "aws_sdk" | "aws_bearer" | "gcp_oauth" | "none";
|
|
1188
|
+
/**
|
|
1189
|
+
* M41 (agent-builder provider framework) — the context a provider's `transform` receives per request. An
|
|
1190
|
+
* object from day one so it can grow without breaking (M42 adds the resolved `credential`).
|
|
1191
|
+
*
|
|
1192
|
+
* @public
|
|
1193
|
+
*/
|
|
1194
|
+
interface ProviderTransformContext {
|
|
1195
|
+
/** The bearer the router resolved for this request (env var, injected key, or an oauth access token). */
|
|
1196
|
+
apiKey: string;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* M41 — the one OPTIONAL behavior seam on a provider profile. It lets a provider own its per-request auth:
|
|
1200
|
+
* `fetch` is the universal seam (a provider that returns its own fetch fully controls headers + refresh, for
|
|
1201
|
+
* every transport that accepts a fetch); `headers` is a convenience merged over `extraHeaders` on transports
|
|
1202
|
+
* that carry them (responses_api). This is the CONTRACT-shape adaptation of OpenCode's provider `auth.loader`
|
|
1203
|
+
* (MIT © 2025 opencode — `packages/core/src/plugin/provider/*.ts`), retargeted to theokit's transport model.
|
|
1204
|
+
* Closes the gap where a `ProviderProfile` could only declare STATIC headers.
|
|
1205
|
+
*
|
|
1206
|
+
* @public
|
|
1207
|
+
*/
|
|
1208
|
+
interface ProviderTransform {
|
|
1209
|
+
/** Dynamic per-request headers, merged OVER the profile's static `extraHeaders`. */
|
|
1210
|
+
headers?(ctx: ProviderTransformContext): Record<string, string>;
|
|
1211
|
+
/** A fetch to use for this provider's requests (refresh-aware / fully provider-controlled). */
|
|
1212
|
+
fetch?(ctx: ProviderTransformContext): typeof fetch;
|
|
1213
|
+
}
|
|
1188
1214
|
interface ProviderProfile {
|
|
1189
1215
|
name: string;
|
|
1190
1216
|
apiMode: ApiMode;
|
|
@@ -1200,6 +1226,11 @@ interface ProviderProfile {
|
|
|
1200
1226
|
fallbackModels: ReadonlyArray<string>;
|
|
1201
1227
|
extraHeaders?: Record<string, string>;
|
|
1202
1228
|
bodyOverrides?: Record<string, unknown>;
|
|
1229
|
+
/**
|
|
1230
|
+
* M41 — the optional per-request behavior seam (dynamic headers + refresh-aware fetch). Absent ⇒ the profile
|
|
1231
|
+
* is pure data and takes the static path byte-for-byte. See {@link ProviderTransform}.
|
|
1232
|
+
*/
|
|
1233
|
+
transform?: ProviderTransform;
|
|
1203
1234
|
/**
|
|
1204
1235
|
* Opt-in leaked-dialect safe-parse (theokit#58 follow-up). When `true`, a chat_completions finish
|
|
1205
1236
|
* with ZERO native `tool_calls` has its assistant content scanned for the Hermes
|
|
@@ -1185,6 +1185,32 @@ interface MemoryProvider {
|
|
|
1185
1185
|
*/
|
|
1186
1186
|
type ApiMode = "chat_completions" | "anthropic_messages" | "responses_api" | "bedrock" | "bedrock_anthropic";
|
|
1187
1187
|
type AuthType = "api_key" | "oauth_device_code" | "oauth_external" | "aws_sdk" | "aws_bearer" | "gcp_oauth" | "none";
|
|
1188
|
+
/**
|
|
1189
|
+
* M41 (agent-builder provider framework) — the context a provider's `transform` receives per request. An
|
|
1190
|
+
* object from day one so it can grow without breaking (M42 adds the resolved `credential`).
|
|
1191
|
+
*
|
|
1192
|
+
* @public
|
|
1193
|
+
*/
|
|
1194
|
+
interface ProviderTransformContext {
|
|
1195
|
+
/** The bearer the router resolved for this request (env var, injected key, or an oauth access token). */
|
|
1196
|
+
apiKey: string;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* M41 — the one OPTIONAL behavior seam on a provider profile. It lets a provider own its per-request auth:
|
|
1200
|
+
* `fetch` is the universal seam (a provider that returns its own fetch fully controls headers + refresh, for
|
|
1201
|
+
* every transport that accepts a fetch); `headers` is a convenience merged over `extraHeaders` on transports
|
|
1202
|
+
* that carry them (responses_api). This is the CONTRACT-shape adaptation of OpenCode's provider `auth.loader`
|
|
1203
|
+
* (MIT © 2025 opencode — `packages/core/src/plugin/provider/*.ts`), retargeted to theokit's transport model.
|
|
1204
|
+
* Closes the gap where a `ProviderProfile` could only declare STATIC headers.
|
|
1205
|
+
*
|
|
1206
|
+
* @public
|
|
1207
|
+
*/
|
|
1208
|
+
interface ProviderTransform {
|
|
1209
|
+
/** Dynamic per-request headers, merged OVER the profile's static `extraHeaders`. */
|
|
1210
|
+
headers?(ctx: ProviderTransformContext): Record<string, string>;
|
|
1211
|
+
/** A fetch to use for this provider's requests (refresh-aware / fully provider-controlled). */
|
|
1212
|
+
fetch?(ctx: ProviderTransformContext): typeof fetch;
|
|
1213
|
+
}
|
|
1188
1214
|
interface ProviderProfile {
|
|
1189
1215
|
name: string;
|
|
1190
1216
|
apiMode: ApiMode;
|
|
@@ -1200,6 +1226,11 @@ interface ProviderProfile {
|
|
|
1200
1226
|
fallbackModels: ReadonlyArray<string>;
|
|
1201
1227
|
extraHeaders?: Record<string, string>;
|
|
1202
1228
|
bodyOverrides?: Record<string, unknown>;
|
|
1229
|
+
/**
|
|
1230
|
+
* M41 — the optional per-request behavior seam (dynamic headers + refresh-aware fetch). Absent ⇒ the profile
|
|
1231
|
+
* is pure data and takes the static path byte-for-byte. See {@link ProviderTransform}.
|
|
1232
|
+
*/
|
|
1233
|
+
transform?: ProviderTransform;
|
|
1203
1234
|
/**
|
|
1204
1235
|
* Opt-in leaked-dialect safe-parse (theokit#58 follow-up). When `true`, a chat_completions finish
|
|
1205
1236
|
* with ZERO native `tool_calls` has its assistant content scanned for the Hermes
|
package/dist/cron.cjs
CHANGED
|
@@ -12953,6 +12953,172 @@ function assistantMessage(message) {
|
|
|
12953
12953
|
return result;
|
|
12954
12954
|
}
|
|
12955
12955
|
|
|
12956
|
+
// src/internal/llm/pool-aware-client.ts
|
|
12957
|
+
init_errors();
|
|
12958
|
+
|
|
12959
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
12960
|
+
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
12961
|
+
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12962
|
+
var CircuitBreaker = class {
|
|
12963
|
+
constructor(opts = {}) {
|
|
12964
|
+
this.opts = opts;
|
|
12965
|
+
}
|
|
12966
|
+
opts;
|
|
12967
|
+
states = /* @__PURE__ */ new Map();
|
|
12968
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12969
|
+
shouldSkip(key) {
|
|
12970
|
+
const state3 = this.states.get(key);
|
|
12971
|
+
if (state3 === void 0) return false;
|
|
12972
|
+
if (state3.cooldownUntilMs === 0) return false;
|
|
12973
|
+
if (this.now() < state3.cooldownUntilMs) return true;
|
|
12974
|
+
state3.cooldownUntilMs = 0;
|
|
12975
|
+
state3.consecutiveTimeouts = 0;
|
|
12976
|
+
return false;
|
|
12977
|
+
}
|
|
12978
|
+
recordSuccess(key) {
|
|
12979
|
+
const state3 = this.states.get(key);
|
|
12980
|
+
if (state3 === void 0) return;
|
|
12981
|
+
state3.consecutiveTimeouts = 0;
|
|
12982
|
+
state3.cooldownUntilMs = 0;
|
|
12983
|
+
}
|
|
12984
|
+
recordTimeout(key) {
|
|
12985
|
+
const state3 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12986
|
+
state3.consecutiveTimeouts += 1;
|
|
12987
|
+
if (state3.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12988
|
+
state3.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12989
|
+
}
|
|
12990
|
+
this.states.set(key, state3);
|
|
12991
|
+
}
|
|
12992
|
+
/** @internal — tests inspect counter state. */
|
|
12993
|
+
inspect(key) {
|
|
12994
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12995
|
+
}
|
|
12996
|
+
now() {
|
|
12997
|
+
return this.opts.now?.() ?? Date.now();
|
|
12998
|
+
}
|
|
12999
|
+
};
|
|
13000
|
+
|
|
13001
|
+
// src/internal/llm/pool-aware-client.ts
|
|
13002
|
+
init_retry();
|
|
13003
|
+
var PoolAwareLlmClient = class {
|
|
13004
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
13005
|
+
this.pool = pool;
|
|
13006
|
+
this.buildClient = buildClient2;
|
|
13007
|
+
this.waitForAvailableMs = waitForAvailableMs;
|
|
13008
|
+
this.name = `pool-aware:${pool.provider}`;
|
|
13009
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
13010
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
13011
|
+
this.rng = resilience.rng;
|
|
13012
|
+
this.onRateLimit = resilience.onRateLimit;
|
|
13013
|
+
}
|
|
13014
|
+
pool;
|
|
13015
|
+
buildClient;
|
|
13016
|
+
waitForAvailableMs;
|
|
13017
|
+
name;
|
|
13018
|
+
onRateLimit;
|
|
13019
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
13020
|
+
breaker;
|
|
13021
|
+
backoffBaseMs;
|
|
13022
|
+
rng;
|
|
13023
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stream() must serialize pool-select → build client → first-event probe → classify → retry/rotate/propagate. Extracting helpers fragments the linear narrative; the comments above each branch keep it readable.
|
|
13024
|
+
async *stream(request, signal) {
|
|
13025
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
13026
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
13027
|
+
code: "circuit_open"
|
|
13028
|
+
});
|
|
13029
|
+
}
|
|
13030
|
+
let hasRetried429 = false;
|
|
13031
|
+
while (true) {
|
|
13032
|
+
if (signal.aborted) throw abortError2(signal);
|
|
13033
|
+
let entry = await this.pool.select();
|
|
13034
|
+
if (entry === null) {
|
|
13035
|
+
if (this.waitForAvailableMs > 0) {
|
|
13036
|
+
const available = await this.pool.waitForAvailable(signal, {
|
|
13037
|
+
maxWaitMs: this.waitForAvailableMs
|
|
13038
|
+
});
|
|
13039
|
+
if (available) {
|
|
13040
|
+
entry = await this.pool.select();
|
|
13041
|
+
}
|
|
13042
|
+
}
|
|
13043
|
+
if (entry === null) {
|
|
13044
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
13045
|
+
throw new CredentialPoolExhaustedError(
|
|
13046
|
+
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
13047
|
+
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
13048
|
+
);
|
|
13049
|
+
}
|
|
13050
|
+
}
|
|
13051
|
+
const realClient = this.buildClient(entry.accessToken);
|
|
13052
|
+
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
13053
|
+
if (attempt.kind === "ok") {
|
|
13054
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
13055
|
+
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
13056
|
+
}
|
|
13057
|
+
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
13058
|
+
if (decision === "retry") {
|
|
13059
|
+
const backoffMs = computeBackoffMs({
|
|
13060
|
+
attempt: 0,
|
|
13061
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
13062
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
13063
|
+
});
|
|
13064
|
+
this.onRateLimit?.({ attempt: 1, retryAfterMs: backoffMs });
|
|
13065
|
+
await sleepWithAbort(backoffMs, signal);
|
|
13066
|
+
hasRetried429 = true;
|
|
13067
|
+
continue;
|
|
13068
|
+
}
|
|
13069
|
+
if (decision === "rotate") {
|
|
13070
|
+
try {
|
|
13071
|
+
await this.pool.markExhaustedAndRotate({
|
|
13072
|
+
entryId: entry.id,
|
|
13073
|
+
statusCode: attempt.error.metadata?.statusCode ?? inferStatusCode(attempt.error),
|
|
13074
|
+
...parseRetryAfterMs(attempt.error) !== void 0 ? { resetAtMs: parseRetryAfterMs(attempt.error) } : {}
|
|
13075
|
+
});
|
|
13076
|
+
} catch (persistErr) {
|
|
13077
|
+
process.stderr.write(
|
|
13078
|
+
`[theokit-sdk] credential-pool: persist failed during rotate; continuing in-memory: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}
|
|
13079
|
+
`
|
|
13080
|
+
);
|
|
13081
|
+
}
|
|
13082
|
+
hasRetried429 = false;
|
|
13083
|
+
continue;
|
|
13084
|
+
}
|
|
13085
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
13086
|
+
throw attempt.error;
|
|
13087
|
+
}
|
|
13088
|
+
}
|
|
13089
|
+
/**
|
|
13090
|
+
* Earliest epoch ms among entries' `lastErrorResetAt` — best estimate
|
|
13091
|
+
* for the caller's `CredentialPoolExhaustedError.nextRetryAt`.
|
|
13092
|
+
*/
|
|
13093
|
+
nextRetryHint() {
|
|
13094
|
+
const resets = this.pool.list().map((e) => e.lastErrorResetAt).filter((v) => v !== void 0);
|
|
13095
|
+
return resets.length === 0 ? void 0 : Math.min(...resets);
|
|
13096
|
+
}
|
|
13097
|
+
};
|
|
13098
|
+
function classifyAndDecide(error, hasRetried429) {
|
|
13099
|
+
if (error instanceof NetworkError) return "propagate";
|
|
13100
|
+
if (error instanceof AuthenticationError) return "rotate";
|
|
13101
|
+
const status = error.metadata?.statusCode ?? 429;
|
|
13102
|
+
if (status === 402) return "rotate";
|
|
13103
|
+
return hasRetried429 ? "rotate" : "retry";
|
|
13104
|
+
}
|
|
13105
|
+
function parseRetryAfterMs(error) {
|
|
13106
|
+
const seconds = error.metadata?.retryAfter;
|
|
13107
|
+
if (typeof seconds === "number" && seconds > 0) {
|
|
13108
|
+
return Date.now() + seconds * 1e3;
|
|
13109
|
+
}
|
|
13110
|
+
return void 0;
|
|
13111
|
+
}
|
|
13112
|
+
function inferStatusCode(error) {
|
|
13113
|
+
if (error instanceof AuthenticationError) return 401;
|
|
13114
|
+
if (error instanceof RateLimitError) return 429;
|
|
13115
|
+
return 0;
|
|
13116
|
+
}
|
|
13117
|
+
function abortError2(signal) {
|
|
13118
|
+
if (signal.reason instanceof Error) return signal.reason;
|
|
13119
|
+
return new Error("AbortError");
|
|
13120
|
+
}
|
|
13121
|
+
|
|
12956
13122
|
// src/internal/llm/responses.ts
|
|
12957
13123
|
function messageToInputItems(message) {
|
|
12958
13124
|
const items = [];
|
|
@@ -13001,7 +13167,9 @@ function buildResponsesBody(request) {
|
|
|
13001
13167
|
for (const message of request.messages) {
|
|
13002
13168
|
for (const item of messageToInputItems(message)) input.push(item);
|
|
13003
13169
|
}
|
|
13004
|
-
const
|
|
13170
|
+
const slash = request.model.lastIndexOf("/");
|
|
13171
|
+
const model = slash >= 0 ? request.model.slice(slash + 1) : request.model;
|
|
13172
|
+
const body = { model, input, stream: true, store: false };
|
|
13005
13173
|
const instructions = collapseSystemText(request.system);
|
|
13006
13174
|
if (instructions.length > 0) body.instructions = instructions;
|
|
13007
13175
|
if (request.maxTokens !== void 0) body.max_output_tokens = request.maxTokens;
|
|
@@ -13135,175 +13303,16 @@ var ResponsesApiClient = class {
|
|
|
13135
13303
|
}
|
|
13136
13304
|
if (toolCalls.length > 0 && stopReason === "end_turn") stopReason = "tool_use";
|
|
13137
13305
|
yield { type: "stop", reason: stopReason };
|
|
13138
|
-
return makeLlmFinish({
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
13147
|
-
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
13148
|
-
var CircuitBreaker = class {
|
|
13149
|
-
constructor(opts = {}) {
|
|
13150
|
-
this.opts = opts;
|
|
13151
|
-
}
|
|
13152
|
-
opts;
|
|
13153
|
-
states = /* @__PURE__ */ new Map();
|
|
13154
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
13155
|
-
shouldSkip(key) {
|
|
13156
|
-
const state3 = this.states.get(key);
|
|
13157
|
-
if (state3 === void 0) return false;
|
|
13158
|
-
if (state3.cooldownUntilMs === 0) return false;
|
|
13159
|
-
if (this.now() < state3.cooldownUntilMs) return true;
|
|
13160
|
-
state3.cooldownUntilMs = 0;
|
|
13161
|
-
state3.consecutiveTimeouts = 0;
|
|
13162
|
-
return false;
|
|
13163
|
-
}
|
|
13164
|
-
recordSuccess(key) {
|
|
13165
|
-
const state3 = this.states.get(key);
|
|
13166
|
-
if (state3 === void 0) return;
|
|
13167
|
-
state3.consecutiveTimeouts = 0;
|
|
13168
|
-
state3.cooldownUntilMs = 0;
|
|
13169
|
-
}
|
|
13170
|
-
recordTimeout(key) {
|
|
13171
|
-
const state3 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13172
|
-
state3.consecutiveTimeouts += 1;
|
|
13173
|
-
if (state3.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
13174
|
-
state3.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
13175
|
-
}
|
|
13176
|
-
this.states.set(key, state3);
|
|
13177
|
-
}
|
|
13178
|
-
/** @internal — tests inspect counter state. */
|
|
13179
|
-
inspect(key) {
|
|
13180
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13181
|
-
}
|
|
13182
|
-
now() {
|
|
13183
|
-
return this.opts.now?.() ?? Date.now();
|
|
13184
|
-
}
|
|
13185
|
-
};
|
|
13186
|
-
|
|
13187
|
-
// src/internal/llm/pool-aware-client.ts
|
|
13188
|
-
init_retry();
|
|
13189
|
-
var PoolAwareLlmClient = class {
|
|
13190
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
13191
|
-
this.pool = pool;
|
|
13192
|
-
this.buildClient = buildClient2;
|
|
13193
|
-
this.waitForAvailableMs = waitForAvailableMs;
|
|
13194
|
-
this.name = `pool-aware:${pool.provider}`;
|
|
13195
|
-
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
13196
|
-
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
13197
|
-
this.rng = resilience.rng;
|
|
13198
|
-
this.onRateLimit = resilience.onRateLimit;
|
|
13199
|
-
}
|
|
13200
|
-
pool;
|
|
13201
|
-
buildClient;
|
|
13202
|
-
waitForAvailableMs;
|
|
13203
|
-
name;
|
|
13204
|
-
onRateLimit;
|
|
13205
|
-
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
13206
|
-
breaker;
|
|
13207
|
-
backoffBaseMs;
|
|
13208
|
-
rng;
|
|
13209
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stream() must serialize pool-select → build client → first-event probe → classify → retry/rotate/propagate. Extracting helpers fragments the linear narrative; the comments above each branch keep it readable.
|
|
13210
|
-
async *stream(request, signal) {
|
|
13211
|
-
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
13212
|
-
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
13213
|
-
code: "circuit_open"
|
|
13214
|
-
});
|
|
13215
|
-
}
|
|
13216
|
-
let hasRetried429 = false;
|
|
13217
|
-
while (true) {
|
|
13218
|
-
if (signal.aborted) throw abortError2(signal);
|
|
13219
|
-
let entry = await this.pool.select();
|
|
13220
|
-
if (entry === null) {
|
|
13221
|
-
if (this.waitForAvailableMs > 0) {
|
|
13222
|
-
const available = await this.pool.waitForAvailable(signal, {
|
|
13223
|
-
maxWaitMs: this.waitForAvailableMs
|
|
13224
|
-
});
|
|
13225
|
-
if (available) {
|
|
13226
|
-
entry = await this.pool.select();
|
|
13227
|
-
}
|
|
13228
|
-
}
|
|
13229
|
-
if (entry === null) {
|
|
13230
|
-
this.breaker.recordTimeout(this.pool.provider);
|
|
13231
|
-
throw new CredentialPoolExhaustedError(
|
|
13232
|
-
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
13233
|
-
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
13234
|
-
);
|
|
13235
|
-
}
|
|
13236
|
-
}
|
|
13237
|
-
const realClient = this.buildClient(entry.accessToken);
|
|
13238
|
-
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
13239
|
-
if (attempt.kind === "ok") {
|
|
13240
|
-
this.breaker.recordSuccess(this.pool.provider);
|
|
13241
|
-
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
13242
|
-
}
|
|
13243
|
-
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
13244
|
-
if (decision === "retry") {
|
|
13245
|
-
const backoffMs = computeBackoffMs({
|
|
13246
|
-
attempt: 0,
|
|
13247
|
-
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
13248
|
-
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
13249
|
-
});
|
|
13250
|
-
this.onRateLimit?.({ attempt: 1, retryAfterMs: backoffMs });
|
|
13251
|
-
await sleepWithAbort(backoffMs, signal);
|
|
13252
|
-
hasRetried429 = true;
|
|
13253
|
-
continue;
|
|
13254
|
-
}
|
|
13255
|
-
if (decision === "rotate") {
|
|
13256
|
-
try {
|
|
13257
|
-
await this.pool.markExhaustedAndRotate({
|
|
13258
|
-
entryId: entry.id,
|
|
13259
|
-
statusCode: attempt.error.metadata?.statusCode ?? inferStatusCode(attempt.error),
|
|
13260
|
-
...parseRetryAfterMs(attempt.error) !== void 0 ? { resetAtMs: parseRetryAfterMs(attempt.error) } : {}
|
|
13261
|
-
});
|
|
13262
|
-
} catch (persistErr) {
|
|
13263
|
-
process.stderr.write(
|
|
13264
|
-
`[theokit-sdk] credential-pool: persist failed during rotate; continuing in-memory: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}
|
|
13265
|
-
`
|
|
13266
|
-
);
|
|
13267
|
-
}
|
|
13268
|
-
hasRetried429 = false;
|
|
13269
|
-
continue;
|
|
13270
|
-
}
|
|
13271
|
-
this.breaker.recordTimeout(this.pool.provider);
|
|
13272
|
-
throw attempt.error;
|
|
13273
|
-
}
|
|
13274
|
-
}
|
|
13275
|
-
/**
|
|
13276
|
-
* Earliest epoch ms among entries' `lastErrorResetAt` — best estimate
|
|
13277
|
-
* for the caller's `CredentialPoolExhaustedError.nextRetryAt`.
|
|
13278
|
-
*/
|
|
13279
|
-
nextRetryHint() {
|
|
13280
|
-
const resets = this.pool.list().map((e) => e.lastErrorResetAt).filter((v) => v !== void 0);
|
|
13281
|
-
return resets.length === 0 ? void 0 : Math.min(...resets);
|
|
13306
|
+
return makeLlmFinish({
|
|
13307
|
+
stopReason,
|
|
13308
|
+
text,
|
|
13309
|
+
toolCalls,
|
|
13310
|
+
inputTokens,
|
|
13311
|
+
outputTokens,
|
|
13312
|
+
reasoningTokens
|
|
13313
|
+
});
|
|
13282
13314
|
}
|
|
13283
13315
|
};
|
|
13284
|
-
function classifyAndDecide(error, hasRetried429) {
|
|
13285
|
-
if (error instanceof NetworkError) return "propagate";
|
|
13286
|
-
if (error instanceof AuthenticationError) return "rotate";
|
|
13287
|
-
const status = error.metadata?.statusCode ?? 429;
|
|
13288
|
-
if (status === 402) return "rotate";
|
|
13289
|
-
return hasRetried429 ? "rotate" : "retry";
|
|
13290
|
-
}
|
|
13291
|
-
function parseRetryAfterMs(error) {
|
|
13292
|
-
const seconds = error.metadata?.retryAfter;
|
|
13293
|
-
if (typeof seconds === "number" && seconds > 0) {
|
|
13294
|
-
return Date.now() + seconds * 1e3;
|
|
13295
|
-
}
|
|
13296
|
-
return void 0;
|
|
13297
|
-
}
|
|
13298
|
-
function inferStatusCode(error) {
|
|
13299
|
-
if (error instanceof AuthenticationError) return 401;
|
|
13300
|
-
if (error instanceof RateLimitError) return 429;
|
|
13301
|
-
return 0;
|
|
13302
|
-
}
|
|
13303
|
-
function abortError2(signal) {
|
|
13304
|
-
if (signal.reason instanceof Error) return signal.reason;
|
|
13305
|
-
return new Error("AbortError");
|
|
13306
|
-
}
|
|
13307
13316
|
|
|
13308
13317
|
// src/internal/llm/vertex-anthropic.ts
|
|
13309
13318
|
init_errors();
|
|
@@ -13676,6 +13685,9 @@ function resolveApiKey2(envVars) {
|
|
|
13676
13685
|
return void 0;
|
|
13677
13686
|
}
|
|
13678
13687
|
function selectTransport(profile, apiKey) {
|
|
13688
|
+
const transformCtx = { apiKey };
|
|
13689
|
+
const transformFetch = profile.transform?.fetch?.(transformCtx);
|
|
13690
|
+
const transformHeaders = profile.transform?.headers?.(transformCtx);
|
|
13679
13691
|
if (profile.apiMode === "chat_completions") {
|
|
13680
13692
|
if (profile.name === "ollama") {
|
|
13681
13693
|
const ollamaBase = process.env.OLLAMA_HOST ?? profile.baseUrl;
|
|
@@ -13692,6 +13704,7 @@ function selectTransport(profile, apiKey) {
|
|
|
13692
13704
|
}
|
|
13693
13705
|
const envOverride = resolveBaseUrlEnvOverride(profile.name);
|
|
13694
13706
|
if (envOverride !== void 0) opts.baseUrl = envOverride;
|
|
13707
|
+
if (transformFetch !== void 0) opts.fetch = transformFetch;
|
|
13695
13708
|
return new OpenAIClient(opts);
|
|
13696
13709
|
}
|
|
13697
13710
|
if (profile.apiMode === "anthropic_messages") {
|
|
@@ -13708,10 +13721,12 @@ function selectTransport(profile, apiKey) {
|
|
|
13708
13721
|
return new BedrockAnthropicClient(realKey !== void 0 ? { apiKey: realKey } : {});
|
|
13709
13722
|
}
|
|
13710
13723
|
if (profile.apiMode === "responses_api") {
|
|
13724
|
+
const mergedHeaders = profile.extraHeaders !== void 0 || transformHeaders !== void 0 ? { ...profile.extraHeaders, ...transformHeaders } : void 0;
|
|
13711
13725
|
return new ResponsesApiClient({
|
|
13712
13726
|
apiKey,
|
|
13713
13727
|
...profile.baseUrl !== void 0 ? { baseUrl: profile.baseUrl } : {},
|
|
13714
|
-
...
|
|
13728
|
+
...mergedHeaders !== void 0 ? { extraHeaders: mergedHeaders } : {},
|
|
13729
|
+
...transformFetch !== void 0 ? { fetch: transformFetch } : {},
|
|
13715
13730
|
providerName: profile.name
|
|
13716
13731
|
});
|
|
13717
13732
|
}
|