@theokit/sdk 4.9.0 → 4.9.1

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.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 body = { model: request.model, input, stream: true, store: false };
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({ stopReason, text, toolCalls, inputTokens, outputTokens, reasoningTokens });
13139
- }
13140
- };
13141
-
13142
- // src/internal/llm/pool-aware-client.ts
13143
- init_errors();
13144
-
13145
- // src/internal/resilience/circuit-breaker.ts
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();