@odla-ai/ai 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -363,6 +363,13 @@ Every error is an `OdlaAIError` subclass carrying a stable `code` — branch on
363
363
  `CapabilityError`, `ContextWindowError`, `InvalidRequestError`, `ToolInputError`,
364
364
  `CancelledError`, `DeadlineExceededError`, `ProviderError`.
365
365
 
366
+ `OdlaAIError.usage` is present only when a provider response was already
367
+ received and a later forced-tool or schema check failed. It is a frozen,
368
+ sanitized token-usage record so accounting can charge a failed structured
369
+ response accurately; it may be absent for transport, authentication, timeout,
370
+ or preflight failures. `toShape()` and streaming `OracleErrorShape` deliberately
371
+ omit it.
372
+
366
373
  ## Develop
367
374
 
368
375
  ```bash
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  toolInput
3
- } from "./chunk-OX4TA4ZH.js";
3
+ } from "./chunk-J2NH2F4B.js";
4
4
  import {
5
5
  addUsage,
6
6
  emptyUsage,
7
7
  mapProviderError,
8
8
  normalizeError,
9
9
  signalWithDeadline
10
- } from "./chunk-PXXCN2EU.js";
10
+ } from "./chunk-ZTY6FLZ5.js";
11
11
 
12
12
  // src/providers/anthropic.ts
13
13
  import Anthropic from "@anthropic-ai/sdk";
@@ -232,4 +232,4 @@ var AnthropicProvider = class {
232
232
  export {
233
233
  AnthropicProvider
234
234
  };
235
- //# sourceMappingURL=anthropic-2NPMHJZT.js.map
235
+ //# sourceMappingURL=anthropic-AGA2NYWP.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ToolInputError
3
- } from "./chunk-PXXCN2EU.js";
3
+ } from "./chunk-ZTY6FLZ5.js";
4
4
 
5
5
  // src/providers/tool-input.ts
6
6
  function toolInput(value, provider, name) {
@@ -13,4 +13,4 @@ function toolInput(value, provider, name) {
13
13
  export {
14
14
  toolInput
15
15
  };
16
- //# sourceMappingURL=chunk-OX4TA4ZH.js.map
16
+ //# sourceMappingURL=chunk-J2NH2F4B.js.map
@@ -18,12 +18,16 @@ var OdlaAIError = class extends Error {
18
18
  code;
19
19
  providerStatus;
20
20
  retryAfter;
21
+ /** Sanitized provider usage when a response was received before a later
22
+ * validation/tool-shape failure. Omitted when usage is genuinely unknown. */
23
+ usage;
21
24
  constructor(code, message, opts) {
22
25
  super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
23
26
  this.name = new.target.name;
24
27
  this.code = code;
25
28
  this.providerStatus = opts?.providerStatus;
26
29
  this.retryAfter = opts?.retryAfter;
30
+ this.usage = opts?.usage ? Object.freeze({ ...opts.usage }) : void 0;
27
31
  }
28
32
  toShape() {
29
33
  return {
@@ -67,7 +71,7 @@ var InvalidRequestError = class extends OdlaAIError {
67
71
  var ToolInputError = class extends OdlaAIError {
68
72
  tool;
69
73
  constructor(message, opts) {
70
- super("tool_input_invalid", message, { cause: opts?.cause });
74
+ super("tool_input_invalid", message, { cause: opts?.cause, usage: opts?.usage });
71
75
  this.tool = opts?.tool;
72
76
  }
73
77
  };
@@ -99,15 +103,28 @@ function statusOf(err) {
99
103
  return void 0;
100
104
  }
101
105
  function retryAfterOf(err) {
102
- if (err && typeof err === "object") {
103
- const headers = err.headers;
104
- if (headers && typeof headers === "object") {
105
- const raw = headers["retry-after"];
106
- const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : NaN;
107
- if (Number.isFinite(n)) return n;
108
- }
109
- }
110
- return void 0;
106
+ if (!err || typeof err !== "object") return void 0;
107
+ const headers = err.headers;
108
+ if (!headers || typeof headers !== "object") return void 0;
109
+ const header = (name) => {
110
+ const get = headers.get;
111
+ if (typeof get === "function") return get.call(headers, name);
112
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name);
113
+ return entry?.[1];
114
+ };
115
+ const milliseconds = numericHeader(header("retry-after-ms"));
116
+ if (milliseconds !== void 0) return milliseconds / 1e3;
117
+ const raw = header("retry-after");
118
+ const seconds = numericHeader(raw);
119
+ if (seconds !== void 0) return seconds;
120
+ if (typeof raw !== "string") return void 0;
121
+ const timestamp = Date.parse(raw);
122
+ if (!Number.isFinite(timestamp)) return void 0;
123
+ return Math.max(0, (timestamp - Date.now()) / 1e3);
124
+ }
125
+ function numericHeader(value) {
126
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
127
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
111
128
  }
112
129
  function messageOf(err) {
113
130
  if (err instanceof Error) return err.message;
@@ -189,4 +206,4 @@ export {
189
206
  signalWithDeadline,
190
207
  throwIfAborted
191
208
  };
192
- //# sourceMappingURL=chunk-PXXCN2EU.js.map
209
+ //# sourceMappingURL=chunk-ZTY6FLZ5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shape/usage.ts","../src/shape/errors.ts","../src/providers/errors.ts","../src/client/abort.ts"],"sourcesContent":["// Token usage accounting. The one runtime island with no dependencies.\n\nexport interface OracleUsage {\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n}\n\n/** A fresh zeroed usage tally (0 input/output tokens, no cache fields) — the seed for accumulation. */\nexport function emptyUsage(): OracleUsage {\n return { inputTokens: 0, outputTokens: 0 };\n}\n\n/** Accumulate `more` into `into`, in place. Cache fields sum when present. */\nexport function addUsage(into: OracleUsage, more: OracleUsage): void {\n into.inputTokens += more.inputTokens;\n into.outputTokens += more.outputTokens;\n if (more.cacheCreationTokens !== undefined) {\n into.cacheCreationTokens = (into.cacheCreationTokens ?? 0) + more.cacheCreationTokens;\n }\n if (more.cacheReadTokens !== undefined) {\n into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;\n }\n}\n","// The normalized error taxonomy and the class hierarchy every adapter throws.\nimport type { OracleUsage } from \"./usage\";\n\n/** The closed taxonomy of normalized error codes carried by every OdlaAIError\n * and by the streaming `error` event. */\nexport type OracleErrorType =\n | \"invalid_request\"\n | \"auth\"\n | \"rate_limit\"\n | \"context_window\"\n | \"cancelled\"\n | \"deadline_exceeded\"\n | \"tool_input_invalid\"\n | \"capability_unsupported\"\n | \"config\"\n | \"provider_error\";\n\n/** The plain-data error shape used inside the streaming `error` event. */\nexport interface OracleErrorShape {\n code: OracleErrorType;\n message: string;\n /** HTTP status from the upstream provider, when applicable. */\n providerStatus?: number;\n /** Seconds until a retry might succeed (rate limit / overload). */\n retryAfter?: number;\n}\n\n/** Base class for every error odla-ai throws. Carries a stable string `code`\n * (the odla ecosystem convention — see odla-db's AuthError/RuleError) so\n * callers branch on `err.code`, never on message text. */\nexport class OdlaAIError extends Error {\n readonly code: OracleErrorType;\n readonly providerStatus?: number;\n readonly retryAfter?: number;\n /** Sanitized provider usage when a response was received before a later\n * validation/tool-shape failure. Omitted when usage is genuinely unknown. */\n readonly usage?: OracleUsage;\n\n constructor(\n code: OracleErrorType,\n message: string,\n opts?: { providerStatus?: number; retryAfter?: number; cause?: unknown; usage?: OracleUsage },\n ) {\n super(message, opts?.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = new.target.name;\n this.code = code;\n this.providerStatus = opts?.providerStatus;\n this.retryAfter = opts?.retryAfter;\n this.usage = opts?.usage ? Object.freeze({ ...opts.usage }) : undefined;\n }\n\n toShape(): OracleErrorShape {\n return {\n code: this.code,\n message: this.message,\n providerStatus: this.providerStatus,\n retryAfter: this.retryAfter,\n };\n }\n}\n\n/** A provider/model is not configured — e.g. no API key for its provider, or an\n * unknown canonical model id. The library analog of odla-db's 501 gating. */\nexport class ConfigError extends OdlaAIError {\n constructor(message: string) {\n super(\"config\", message);\n }\n}\n\n/** Authentication with the provider failed (bad/missing key). */\nexport class AuthError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"auth\", message, opts);\n }\n}\n\n/** The provider rate-limited or is overloaded; check `retryAfter`. */\nexport class RateLimitError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; retryAfter?: number; cause?: unknown }) {\n super(\"rate_limit\", message, opts);\n }\n}\n\n/** The request asked a model to do something it cannot — audio to Claude, tools\n * to a text-only model, web search where unsupported, etc. Raised before any\n * network call by `validateRequest`. */\nexport class CapabilityError extends OdlaAIError {\n constructor(message: string) {\n super(\"capability_unsupported\", message);\n }\n}\n\n/** The request exceeded the model's context window. */\nexport class ContextWindowError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"context_window\", message, opts);\n }\n}\n\n/** The request was malformed / rejected as invalid by the provider. */\nexport class InvalidRequestError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"invalid_request\", message, opts);\n }\n}\n\n/** A provider returned tool arguments that are malformed or do not satisfy the\n * declared JSON Schema. Tool handlers are never invoked for this error. */\nexport class ToolInputError extends OdlaAIError {\n readonly tool?: string;\n\n constructor(message: string, opts?: { tool?: string; cause?: unknown; usage?: OracleUsage }) {\n super(\"tool_input_invalid\", message, { cause: opts?.cause, usage: opts?.usage });\n this.tool = opts?.tool;\n }\n}\n\n/** The caller cancelled a request. This is not an upstream failure and should\n * not be retried unless the caller explicitly starts a new operation. */\nexport class CancelledError extends OdlaAIError {\n constructor(message = \"AI request was cancelled.\", opts?: { cause?: unknown }) {\n super(\"cancelled\", message, opts);\n }\n}\n\n/** The request's absolute deadline elapsed. Callers may choose a new deadline,\n * but should not classify this as a provider outage. */\nexport class DeadlineExceededError extends OdlaAIError {\n constructor(message = \"AI request deadline was exceeded.\", opts?: { cause?: unknown }) {\n super(\"deadline_exceeded\", message, opts);\n }\n}\n\n/** Any other upstream provider failure (5xx, transport, unexpected shape). */\nexport class ProviderError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown; usage?: OracleUsage }) {\n super(\"provider_error\", message, opts);\n }\n}\n","// Normalize a thrown provider-SDK error into the odla-ai error taxonomy. Kept\n// dependency-light: it duck-types the HTTP status / retry-after off the error\n// object rather than importing each SDK's error classes, so it works uniformly\n// across @anthropic-ai/sdk, openai, and @google/genai.\n\nimport {\n AuthError,\n CancelledError,\n ContextWindowError,\n DeadlineExceededError,\n InvalidRequestError,\n OdlaAIError,\n ProviderError,\n RateLimitError,\n type ProviderId,\n} from \"../shape/index\";\n\nfunction statusOf(err: unknown): number | undefined {\n if (err && typeof err === \"object\") {\n const rec = err as Record<string, unknown>;\n for (const k of [\"status\", \"statusCode\", \"code\"]) {\n const v = rec[k];\n if (typeof v === \"number\") return v;\n }\n }\n return undefined;\n}\n\nfunction retryAfterOf(err: unknown): number | undefined {\n if (!err || typeof err !== \"object\") return undefined;\n const headers = (err as { headers?: unknown }).headers;\n if (!headers || typeof headers !== \"object\") return undefined;\n\n // The OpenAI and Anthropic SDKs expose a web `Headers` instance. Some\n // custom transports expose a plain object instead, occasionally preserving\n // the provider's original header casing. Support both without importing an\n // SDK-specific error type.\n const header = (name: string): unknown => {\n const get = (headers as { get?: unknown }).get;\n if (typeof get === \"function\") return get.call(headers, name);\n const entry = Object.entries(headers as Record<string, unknown>)\n .find(([key]) => key.toLowerCase() === name);\n return entry?.[1];\n };\n\n const milliseconds = numericHeader(header(\"retry-after-ms\"));\n if (milliseconds !== undefined) return milliseconds / 1_000;\n\n const raw = header(\"retry-after\");\n const seconds = numericHeader(raw);\n if (seconds !== undefined) return seconds;\n if (typeof raw !== \"string\") return undefined;\n const timestamp = Date.parse(raw);\n if (!Number.isFinite(timestamp)) return undefined;\n return Math.max(0, (timestamp - Date.now()) / 1_000);\n}\n\nfunction numericHeader(value: unknown): number | undefined {\n const parsed = typeof value === \"number\" ? value : typeof value === \"string\" && value.trim() ? Number(value) : NaN;\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n if (typeof err === \"string\") return err;\n return \"unknown provider error\";\n}\n\n/** Map any provider error to an OdlaAIError, returning it (does not throw).\n * Re-returns an OdlaAIError unchanged (already normalized). */\nexport function normalizeError(err: unknown, provider: ProviderId, signal?: AbortSignal): OdlaAIError {\n if (err instanceof OdlaAIError) return err;\n const cancellation = cancellationError(signal?.aborted ? signal.reason : err);\n if (signal?.aborted || cancellation) return cancellation ?? new CancelledError(undefined, { cause: err });\n\n const status = statusOf(err);\n const message = messageOf(err);\n const tagged = `[${provider}] ${message}`;\n\n if (status === 401 || status === 403) return new AuthError(tagged, { providerStatus: status, cause: err });\n if (status === 429) {\n return new RateLimitError(tagged, { providerStatus: status, retryAfter: retryAfterOf(err), cause: err });\n }\n if (status === 400 || status === 422) {\n if (/context|token limit|too long|maximum context/i.test(message)) {\n return new ContextWindowError(tagged, { providerStatus: status, cause: err });\n }\n return new InvalidRequestError(tagged, { providerStatus: status, cause: err });\n }\n return new ProviderError(tagged, { providerStatus: status, cause: err });\n}\n\n/**\n * Normalize and throw. The `never` return lets call sites write\n * `catch (e) { mapProviderError(e, id); }` with no fallthrough.\n */\nexport function mapProviderError(err: unknown, provider: ProviderId, signal?: AbortSignal): never {\n throw normalizeError(err, provider, signal);\n}\n\nfunction cancellationError(reason: unknown): CancelledError | DeadlineExceededError | undefined {\n if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) return reason;\n if (!reason || typeof reason !== \"object\") return undefined;\n const rec = reason as { name?: unknown; code?: unknown; message?: unknown };\n if (rec.name === \"TimeoutError\") return new DeadlineExceededError(undefined, { cause: reason });\n if (rec.name === \"AbortError\" || rec.name === \"APIUserAbortError\" || rec.code === \"ABORT_ERR\") {\n return new CancelledError(undefined, { cause: reason });\n }\n return undefined;\n}\n","import { CancelledError, ConfigError, DeadlineExceededError } from \"../shape/errors\";\n\n/** Combine a caller signal with an absolute deadline. Kept provider-neutral so\n * every adapter and the agent loop observes the same cancellation contract. */\nexport function signalWithDeadline(signal?: AbortSignal, deadline?: number): AbortSignal | undefined {\n if (deadline === undefined) return signal;\n if (!Number.isSafeInteger(deadline)) throw new ConfigError(\"deadline must be a finite integer Unix timestamp in milliseconds.\");\n const remaining = deadline - Date.now();\n if (remaining <= 0) {\n const expired = new AbortController();\n expired.abort(new DeadlineExceededError());\n return signal ? AbortSignal.any([signal, expired.signal]) : expired.signal;\n }\n const timeout = AbortSignal.timeout(remaining);\n return signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\n/** Fail synchronously before beginning a local side effect when a run was\n * cancelled or its deadline elapsed. Handlers must still observe the signal\n * while in flight; this closes the already-aborted race at invocation time. */\nexport function throwIfAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return;\n const reason = signal.reason;\n if (reason instanceof DeadlineExceededError || reason instanceof CancelledError) throw reason;\n if (reason && typeof reason === \"object\" && (reason as { name?: unknown }).name === \"TimeoutError\") {\n throw new DeadlineExceededError(undefined, { cause: reason });\n }\n throw new CancelledError(undefined, { cause: reason });\n}\n"],"mappings":"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","names":[]}
@@ -3,14 +3,14 @@ import {
3
3
  } from "./chunk-U7F6VJCV.js";
4
4
  import {
5
5
  toolInput
6
- } from "./chunk-OX4TA4ZH.js";
6
+ } from "./chunk-J2NH2F4B.js";
7
7
  import {
8
8
  CapabilityError,
9
9
  emptyUsage,
10
10
  mapProviderError,
11
11
  normalizeError,
12
12
  signalWithDeadline
13
- } from "./chunk-PXXCN2EU.js";
13
+ } from "./chunk-ZTY6FLZ5.js";
14
14
 
15
15
  // src/providers/google.ts
16
16
  import { GoogleGenAI } from "@google/genai";
@@ -222,4 +222,4 @@ export {
222
222
  mapResponse,
223
223
  toContents
224
224
  };
225
- //# sourceMappingURL=google-3XV2VWVB.js.map
225
+ //# sourceMappingURL=google-H5CCSLJ2.js.map
package/dist/index.cjs CHANGED
@@ -85,12 +85,16 @@ var init_errors = __esm({
85
85
  code;
86
86
  providerStatus;
87
87
  retryAfter;
88
+ /** Sanitized provider usage when a response was received before a later
89
+ * validation/tool-shape failure. Omitted when usage is genuinely unknown. */
90
+ usage;
88
91
  constructor(code, message, opts) {
89
92
  super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
90
93
  this.name = new.target.name;
91
94
  this.code = code;
92
95
  this.providerStatus = opts?.providerStatus;
93
96
  this.retryAfter = opts?.retryAfter;
97
+ this.usage = opts?.usage ? Object.freeze({ ...opts.usage }) : void 0;
94
98
  }
95
99
  toShape() {
96
100
  return {
@@ -134,7 +138,7 @@ var init_errors = __esm({
134
138
  ToolInputError = class extends OdlaAIError {
135
139
  tool;
136
140
  constructor(message, opts) {
137
- super("tool_input_invalid", message, { cause: opts?.cause });
141
+ super("tool_input_invalid", message, { cause: opts?.cause, usage: opts?.usage });
138
142
  this.tool = opts?.tool;
139
143
  }
140
144
  };
@@ -585,15 +589,28 @@ function statusOf(err) {
585
589
  return void 0;
586
590
  }
587
591
  function retryAfterOf(err) {
588
- if (err && typeof err === "object") {
589
- const headers = err.headers;
590
- if (headers && typeof headers === "object") {
591
- const raw = headers["retry-after"];
592
- const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : NaN;
593
- if (Number.isFinite(n)) return n;
594
- }
595
- }
596
- return void 0;
592
+ if (!err || typeof err !== "object") return void 0;
593
+ const headers = err.headers;
594
+ if (!headers || typeof headers !== "object") return void 0;
595
+ const header = (name) => {
596
+ const get = headers.get;
597
+ if (typeof get === "function") return get.call(headers, name);
598
+ const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name);
599
+ return entry?.[1];
600
+ };
601
+ const milliseconds = numericHeader(header("retry-after-ms"));
602
+ if (milliseconds !== void 0) return milliseconds / 1e3;
603
+ const raw = header("retry-after");
604
+ const seconds = numericHeader(raw);
605
+ if (seconds !== void 0) return seconds;
606
+ if (typeof raw !== "string") return void 0;
607
+ const timestamp = Date.parse(raw);
608
+ if (!Number.isFinite(timestamp)) return void 0;
609
+ return Math.max(0, (timestamp - Date.now()) / 1e3);
610
+ }
611
+ function numericHeader(value) {
612
+ const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : NaN;
613
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
597
614
  }
598
615
  function messageOf(err) {
599
616
  if (err instanceof Error) return err.message;
@@ -1920,12 +1937,12 @@ function init(opts = {}) {
1920
1937
  const res = await provider.create(spec, req);
1921
1938
  const block = res.content.find((b) => b.type === "tool_use" && b.name === c.tool.name);
1922
1939
  if (!block || block.type !== "tool_use") {
1923
- throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`);
1940
+ throw new ProviderError(`Model "${model}" did not return the forced tool call "${c.tool.name}".`, { usage: res.usage });
1924
1941
  }
1925
1942
  const validation = validateJsonSchema(c.tool.parameters, block.input);
1926
1943
  if (!validation.valid) {
1927
1944
  const detail = validation.issues.slice(0, 5).map((i) => `${i.path} ${i.message}`).join("; ");
1928
- throw new ToolInputError(`Tool "${c.tool.name}" returned invalid input: ${detail}`, { tool: c.tool.name });
1945
+ throw new ToolInputError(`Tool "${c.tool.name}" returned invalid input: ${detail}`, { tool: c.tool.name, usage: res.usage });
1929
1946
  }
1930
1947
  return { value: block.input, usage: res.usage };
1931
1948
  };
@@ -2102,6 +2119,11 @@ Every error is an OdlaAIError subclass with a stable \`code\` \u2014 branch on e
2102
2119
  config, auth, rate_limit, capability_unsupported, context_window, invalid_request,
2103
2120
  tool_input_invalid, cancelled, deadline_exceeded, provider_error.
2104
2121
 
2122
+ OdlaAIError.usage is an optional frozen token-usage record. It exists only when
2123
+ the provider returned a response before a later forced-tool/schema validation
2124
+ failure, and may be absent for every earlier failure. It is intentionally not
2125
+ included in toShape() or streaming OracleErrorShape.
2126
+
2105
2127
  ## Models
2106
2128
 
2107
2129
  ${models}