@odla-ai/ai 0.1.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/README.md +290 -0
- package/dist/anthropic-JXDXR7O7.js +219 -0
- package/dist/anthropic-JXDXR7O7.js.map +1 -0
- package/dist/chunk-LANGYP65.js +173 -0
- package/dist/chunk-LANGYP65.js.map +1 -0
- package/dist/google-3TFOFIQO.js +206 -0
- package/dist/google-3TFOFIQO.js.map +1 -0
- package/dist/index.cjs +2002 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +991 -0
- package/dist/index.d.ts +991 -0
- package/dist/index.js +1122 -0
- package/dist/index.js.map +1 -0
- package/dist/openai-Y3OAONAU.js +257 -0
- package/dist/openai-Y3OAONAU.js.map +1 -0
- package/llms.txt +111 -0
- package/package.json +76 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/shape/index.ts
|
|
2
|
+
function emptyUsage() {
|
|
3
|
+
return { inputTokens: 0, outputTokens: 0 };
|
|
4
|
+
}
|
|
5
|
+
function addUsage(into, more) {
|
|
6
|
+
into.inputTokens += more.inputTokens;
|
|
7
|
+
into.outputTokens += more.outputTokens;
|
|
8
|
+
if (more.cacheCreationTokens !== void 0) {
|
|
9
|
+
into.cacheCreationTokens = (into.cacheCreationTokens ?? 0) + more.cacheCreationTokens;
|
|
10
|
+
}
|
|
11
|
+
if (more.cacheReadTokens !== void 0) {
|
|
12
|
+
into.cacheReadTokens = (into.cacheReadTokens ?? 0) + more.cacheReadTokens;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var OdlaAIError = class extends Error {
|
|
16
|
+
code;
|
|
17
|
+
providerStatus;
|
|
18
|
+
retryAfter;
|
|
19
|
+
constructor(code, message, opts) {
|
|
20
|
+
super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
21
|
+
this.name = new.target.name;
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.providerStatus = opts?.providerStatus;
|
|
24
|
+
this.retryAfter = opts?.retryAfter;
|
|
25
|
+
}
|
|
26
|
+
toShape() {
|
|
27
|
+
return {
|
|
28
|
+
code: this.code,
|
|
29
|
+
message: this.message,
|
|
30
|
+
providerStatus: this.providerStatus,
|
|
31
|
+
retryAfter: this.retryAfter
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var ConfigError = class extends OdlaAIError {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super("config", message);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var AuthError = class extends OdlaAIError {
|
|
41
|
+
constructor(message, opts) {
|
|
42
|
+
super("auth", message, opts);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var RateLimitError = class extends OdlaAIError {
|
|
46
|
+
constructor(message, opts) {
|
|
47
|
+
super("rate_limit", message, opts);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var CapabilityError = class extends OdlaAIError {
|
|
51
|
+
constructor(message) {
|
|
52
|
+
super("capability_unsupported", message);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ContextWindowError = class extends OdlaAIError {
|
|
56
|
+
constructor(message, opts) {
|
|
57
|
+
super("context_window", message, opts);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var InvalidRequestError = class extends OdlaAIError {
|
|
61
|
+
constructor(message, opts) {
|
|
62
|
+
super("invalid_request", message, opts);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var ProviderError = class extends OdlaAIError {
|
|
66
|
+
constructor(message, opts) {
|
|
67
|
+
super("provider_error", message, opts);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
function isTextBlock(b) {
|
|
71
|
+
return b.type === "text";
|
|
72
|
+
}
|
|
73
|
+
function isImageBlock(b) {
|
|
74
|
+
return b.type === "image";
|
|
75
|
+
}
|
|
76
|
+
function isAudioBlock(b) {
|
|
77
|
+
return b.type === "audio";
|
|
78
|
+
}
|
|
79
|
+
function isDocumentBlock(b) {
|
|
80
|
+
return b.type === "document";
|
|
81
|
+
}
|
|
82
|
+
function isToolUseBlock(b) {
|
|
83
|
+
return b.type === "tool_use";
|
|
84
|
+
}
|
|
85
|
+
function isToolResultBlock(b) {
|
|
86
|
+
return b.type === "tool_result";
|
|
87
|
+
}
|
|
88
|
+
function isThinkingBlock(b) {
|
|
89
|
+
return b.type === "thinking";
|
|
90
|
+
}
|
|
91
|
+
function blocksOf(content) {
|
|
92
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
93
|
+
}
|
|
94
|
+
function extractText(content) {
|
|
95
|
+
return content.filter(isTextBlock).map((b) => b.text).join("");
|
|
96
|
+
}
|
|
97
|
+
function extractToolUses(content) {
|
|
98
|
+
return content.filter(isToolUseBlock);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/providers/errors.ts
|
|
102
|
+
function statusOf(err) {
|
|
103
|
+
if (err && typeof err === "object") {
|
|
104
|
+
const rec = err;
|
|
105
|
+
for (const k of ["status", "statusCode", "code"]) {
|
|
106
|
+
const v = rec[k];
|
|
107
|
+
if (typeof v === "number") return v;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
function retryAfterOf(err) {
|
|
113
|
+
if (err && typeof err === "object") {
|
|
114
|
+
const headers = err.headers;
|
|
115
|
+
if (headers && typeof headers === "object") {
|
|
116
|
+
const raw = headers["retry-after"];
|
|
117
|
+
const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : NaN;
|
|
118
|
+
if (Number.isFinite(n)) return n;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
function messageOf(err) {
|
|
124
|
+
if (err instanceof Error) return err.message;
|
|
125
|
+
if (typeof err === "string") return err;
|
|
126
|
+
return "unknown provider error";
|
|
127
|
+
}
|
|
128
|
+
function normalizeError(err, provider) {
|
|
129
|
+
if (err instanceof OdlaAIError) return err;
|
|
130
|
+
const status = statusOf(err);
|
|
131
|
+
const message = messageOf(err);
|
|
132
|
+
const tagged = `[${provider}] ${message}`;
|
|
133
|
+
if (status === 401 || status === 403) return new AuthError(tagged, { providerStatus: status, cause: err });
|
|
134
|
+
if (status === 429) {
|
|
135
|
+
return new RateLimitError(tagged, { providerStatus: status, retryAfter: retryAfterOf(err), cause: err });
|
|
136
|
+
}
|
|
137
|
+
if (status === 400 || status === 422) {
|
|
138
|
+
if (/context|token limit|too long|maximum context/i.test(message)) {
|
|
139
|
+
return new ContextWindowError(tagged, { providerStatus: status, cause: err });
|
|
140
|
+
}
|
|
141
|
+
return new InvalidRequestError(tagged, { providerStatus: status, cause: err });
|
|
142
|
+
}
|
|
143
|
+
return new ProviderError(tagged, { providerStatus: status, cause: err });
|
|
144
|
+
}
|
|
145
|
+
function mapProviderError(err, provider) {
|
|
146
|
+
throw normalizeError(err, provider);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
emptyUsage,
|
|
151
|
+
addUsage,
|
|
152
|
+
OdlaAIError,
|
|
153
|
+
ConfigError,
|
|
154
|
+
AuthError,
|
|
155
|
+
RateLimitError,
|
|
156
|
+
CapabilityError,
|
|
157
|
+
ContextWindowError,
|
|
158
|
+
InvalidRequestError,
|
|
159
|
+
ProviderError,
|
|
160
|
+
isTextBlock,
|
|
161
|
+
isImageBlock,
|
|
162
|
+
isAudioBlock,
|
|
163
|
+
isDocumentBlock,
|
|
164
|
+
isToolUseBlock,
|
|
165
|
+
isToolResultBlock,
|
|
166
|
+
isThinkingBlock,
|
|
167
|
+
blocksOf,
|
|
168
|
+
extractText,
|
|
169
|
+
extractToolUses,
|
|
170
|
+
normalizeError,
|
|
171
|
+
mapProviderError
|
|
172
|
+
};
|
|
173
|
+
//# sourceMappingURL=chunk-LANGYP65.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shape/index.ts","../src/providers/errors.ts"],"sourcesContent":["// The canonical request / response / event types for talking to any LLM\n// provider through odla-ai. One shape regardless of which vendor answers.\n//\n// Modeled after Anthropic's content-block shape because: (a) odla is\n// Claude-native in spirit, (b) Anthropic's tool-use is cleaner (object `input`\n// instead of a JSON-string `arguments`, structured content blocks, tool_result\n// as a content block), and (c) translating OpenAI/Google → Anthropic loses less\n// fidelity than the reverse. Forked and generalized from odla-ai-old's\n// `@odla-ai/oracle-shape`: this version adds Google as a first-class provider,\n// adds audio (and PDF document) input, replaces two-provider hardcoding with an\n// open provider union, and generalizes reasoning controls.\n//\n// This module has ZERO runtime dependencies. It is the load-bearing contract the\n// adapters, the client facade, the agent loop, and the eval harness all speak;\n// provider SDK types never leak past the adapters.\n\n// ----- Providers -----\n\n/** Every provider odla-ai can route to. Apps should not branch on this for\n * portability, but it is carried on responses/events for observability. */\nexport type ProviderId = \"anthropic\" | \"openai\" | \"google\";\n\n// ----- Content blocks -----\n\nexport interface TextBlock {\n type: \"text\";\n text: string;\n /** Anthropic prompt-caching marker. Other providers ignore it. */\n cacheControl?: { type: \"ephemeral\" };\n}\n\nexport type ImageMediaType = \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\nexport type ImageSource =\n | { type: \"base64\"; mediaType: ImageMediaType; data: string }\n | { type: \"url\"; url: string };\n\nexport interface ImageBlock {\n type: \"image\";\n source: ImageSource;\n}\n\nexport type AudioMediaType =\n | \"audio/wav\"\n | \"audio/mp3\"\n | \"audio/mpeg\"\n | \"audio/ogg\"\n | \"audio/flac\"\n | \"audio/aac\"\n | \"audio/webm\";\n\nexport type AudioSource =\n | { type: \"base64\"; mediaType: AudioMediaType; data: string }\n | { type: \"url\"; url: string };\n\n/** Audio input. Supported by OpenAI (audio models) and Google (Gemini); NOT by\n * Anthropic — a request carrying an AudioBlock to a Claude model is rejected up\n * front by capability validation, never sent. */\nexport interface AudioBlock {\n type: \"audio\";\n source: AudioSource;\n}\n\nexport type DocumentSource =\n | { type: \"base64\"; mediaType: \"application/pdf\"; data: string }\n | { type: \"url\"; url: string };\n\n/** A document (PDF) input. Supported by Anthropic and Google. */\nexport interface DocumentBlock {\n type: \"document\";\n source: DocumentSource;\n}\n\nexport interface ToolUseBlock {\n type: \"tool_use\";\n /** Correlates a `tool_result` with this call. Translated from OpenAI's\n * `tool_calls[].id` by the openai adapter. */\n id: string;\n name: string;\n /** Already-parsed object. OpenAI's JSON-string `arguments` is parsed before it\n * reaches this shape — callers never JSON.parse a tool's arguments. */\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n type: \"tool_result\";\n toolUseId: string;\n /** String for plain-text results (most tools); a content-block array for tools\n * that return structured / multi-modal content. */\n content: string | OracleContentBlock[];\n isError?: boolean;\n}\n\nexport interface ThinkingBlock {\n type: \"thinking\";\n /** Extended-thinking text (Anthropic). Empty when `display` is omitted. */\n thinking: string;\n signature?: string;\n}\n\nexport type OracleContentBlock =\n | TextBlock\n | ImageBlock\n | AudioBlock\n | DocumentBlock\n | ToolUseBlock\n | ToolResultBlock\n | ThinkingBlock;\n\n// ----- Messages -----\n\nexport type OracleRole = \"user\" | \"assistant\";\n\nexport interface OracleMessage {\n role: OracleRole;\n /** A plain string (shorthand for a single TextBlock) or an explicit array for\n * multi-modal / tool turns. */\n content: string | OracleContentBlock[];\n}\n\n// ----- Tools -----\n\nexport interface OracleTool {\n name: string;\n description: string;\n /** JSON Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** How the model may use tools this turn. `{ type: \"tool\", name }` forces one\n * specific tool — the mechanism behind `extract<T>()`. */\nexport type ToolChoice = \"auto\" | \"any\" | \"none\" | { type: \"tool\"; name: string };\n\n// ----- Reasoning / output controls -----\n\n/** Whether the model reasons before answering. Maps to Anthropic adaptive\n * thinking; emulated or ignored on providers without a native equivalent. */\nexport type ThinkingMode = \"off\" | \"adaptive\";\n\n/** Reasoning/spend depth. Maps to Anthropic `output_config.effort`; the openai\n * and google adapters map it to their nearest reasoning-effort knob. */\nexport type Effort = \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\";\n\n/** Constrain the response to valid JSON matching a schema (structured output). */\nexport interface ResponseFormat {\n type: \"json_schema\";\n name?: string;\n schema: Record<string, unknown>;\n}\n\n// ----- Request -----\n\nexport interface OracleRequest {\n /** Canonical model id from the catalog; resolved to a provider + native id. */\n model: string;\n /** System prompt. Top-level for Anthropic/Google; the openai adapter folds it\n * into a leading system message. */\n system?: string | TextBlock[];\n messages: OracleMessage[];\n tools?: OracleTool[];\n toolChoice?: ToolChoice;\n maxTokens: number;\n temperature?: number;\n stopSequences?: string[];\n /** Reasoning mode. Only applied on models whose capabilities allow it. */\n thinking?: ThinkingMode;\n /** Reasoning/spend depth. Only applied on models whose capabilities allow it. */\n effort?: Effort;\n responseFormat?: ResponseFormat;\n /** Enable the provider's server-side web-search tool for this request. */\n webSearch?: boolean;\n /** Provider-specific escape hatch, passed through opaquely. Using it gives up\n * portability across providers. */\n providerExtras?: Record<string, unknown>;\n}\n\n// ----- Usage -----\n\nexport interface OracleUsage {\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n}\n\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\n// ----- Response (non-streaming) -----\n\nexport type OracleStopReason =\n | \"end_turn\"\n | \"max_tokens\"\n | \"stop_sequence\"\n | \"tool_use\"\n | \"pause_turn\"\n | \"refusal\"\n | \"error\";\n\nexport interface OracleResponse {\n id: string;\n model: string;\n /** The provider that actually answered — for observability, not branching. */\n provider: ProviderId;\n role: \"assistant\";\n content: OracleContentBlock[];\n stopReason: OracleStopReason;\n usage: OracleUsage;\n}\n\n// ----- Streaming events -----\n\n/** A discriminated union of SSE events with the same vocabulary regardless of\n * provider. The anthropic adapter passes these through nearly 1:1; the openai\n * and google adapters map their native streams onto this shape. */\nexport type OracleEvent =\n | MessageStartEvent\n | ContentBlockStartEvent\n | ContentBlockDeltaEvent\n | ContentBlockStopEvent\n | MessageDeltaEvent\n | MessageStopEvent\n | OracleErrorEvent;\n\nexport interface MessageStartEvent {\n type: \"message_start\";\n message: {\n id: string;\n model: string;\n provider: ProviderId;\n role: \"assistant\";\n content: [];\n usage: OracleUsage;\n };\n}\n\nexport interface ContentBlockStartEvent {\n type: \"content_block_start\";\n index: number;\n contentBlock: OracleContentBlock;\n}\n\nexport type ContentBlockDelta =\n | { type: \"text_delta\"; text: string }\n | { type: \"thinking_delta\"; thinking: string }\n | { type: \"input_json_delta\"; partialJson: string };\n\nexport interface ContentBlockDeltaEvent {\n type: \"content_block_delta\";\n index: number;\n delta: ContentBlockDelta;\n}\n\nexport interface ContentBlockStopEvent {\n type: \"content_block_stop\";\n index: number;\n}\n\nexport interface MessageDeltaEvent {\n type: \"message_delta\";\n delta: { stopReason?: OracleStopReason };\n usage: OracleUsage;\n}\n\nexport interface MessageStopEvent {\n type: \"message_stop\";\n}\n\nexport interface OracleErrorEvent {\n type: \"error\";\n error: OracleErrorShape;\n}\n\n// ----- Errors -----\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 | \"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\n constructor(\n code: OracleErrorType,\n message: string,\n opts?: { providerStatus?: number; retryAfter?: number; cause?: unknown },\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 }\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/** Any other upstream provider failure (5xx, transport, unexpected shape). */\nexport class ProviderError extends OdlaAIError {\n constructor(message: string, opts?: { providerStatus?: number; cause?: unknown }) {\n super(\"provider_error\", message, opts);\n }\n}\n\n// ----- Helpers (cheap, dependency-free) -----\n\nexport function isTextBlock(b: OracleContentBlock): b is TextBlock {\n return b.type === \"text\";\n}\nexport function isImageBlock(b: OracleContentBlock): b is ImageBlock {\n return b.type === \"image\";\n}\nexport function isAudioBlock(b: OracleContentBlock): b is AudioBlock {\n return b.type === \"audio\";\n}\nexport function isDocumentBlock(b: OracleContentBlock): b is DocumentBlock {\n return b.type === \"document\";\n}\nexport function isToolUseBlock(b: OracleContentBlock): b is ToolUseBlock {\n return b.type === \"tool_use\";\n}\nexport function isToolResultBlock(b: OracleContentBlock): b is ToolResultBlock {\n return b.type === \"tool_result\";\n}\nexport function isThinkingBlock(b: OracleContentBlock): b is ThinkingBlock {\n return b.type === \"thinking\";\n}\n\n/** Normalize a message's `content` (string shorthand or block array) to blocks. */\nexport function blocksOf(content: string | OracleContentBlock[]): OracleContentBlock[] {\n return typeof content === \"string\" ? [{ type: \"text\", text: content }] : content;\n}\n\n/** Concatenate the text of every TextBlock. Image/audio/tool/thinking ignored. */\nexport function extractText(content: OracleContentBlock[]): string {\n return content.filter(isTextBlock).map((b) => b.text).join(\"\");\n}\n\n/** Collect every tool_use block from response or message content. */\nexport function extractToolUses(content: OracleContentBlock[]): ToolUseBlock[] {\n return content.filter(isToolUseBlock);\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 ContextWindowError,\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\") {\n const headers = (err as { headers?: unknown }).headers;\n if (headers && typeof headers === \"object\") {\n const raw = (headers as Record<string, unknown>)[\"retry-after\"];\n const n = typeof raw === \"string\" ? Number(raw) : typeof raw === \"number\" ? raw : NaN;\n if (Number.isFinite(n)) return n;\n }\n }\n return 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): OdlaAIError {\n if (err instanceof OdlaAIError) return 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): never {\n throw normalizeError(err, provider);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CapabilityError,
|
|
3
|
+
emptyUsage,
|
|
4
|
+
mapProviderError,
|
|
5
|
+
normalizeError
|
|
6
|
+
} from "./chunk-LANGYP65.js";
|
|
7
|
+
|
|
8
|
+
// src/providers/google.ts
|
|
9
|
+
import { GoogleGenAI, FunctionCallingConfigMode } from "@google/genai";
|
|
10
|
+
var GoogleProvider = class {
|
|
11
|
+
id = "google";
|
|
12
|
+
client;
|
|
13
|
+
constructor(apiKey, client) {
|
|
14
|
+
this.client = client ?? new GoogleGenAI({ apiKey });
|
|
15
|
+
}
|
|
16
|
+
async create(spec, req) {
|
|
17
|
+
try {
|
|
18
|
+
const res = await this.client.models.generateContent(buildParams(spec, req));
|
|
19
|
+
return mapResponse(res, req);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
mapProviderError(err, "google");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async *stream(spec, req) {
|
|
25
|
+
try {
|
|
26
|
+
const iter = await this.client.models.generateContentStream(buildParams(spec, req));
|
|
27
|
+
yield* mapStream(iter, req);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
yield { type: "error", error: normalizeError(err, "google").toShape() };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
function buildParams(spec, req) {
|
|
34
|
+
const config = { maxOutputTokens: req.maxTokens };
|
|
35
|
+
if (req.system !== void 0) {
|
|
36
|
+
config.systemInstruction = typeof req.system === "string" ? req.system : req.system.map((b) => b.text).join("");
|
|
37
|
+
}
|
|
38
|
+
if (req.temperature !== void 0) config.temperature = req.temperature;
|
|
39
|
+
if (req.stopSequences && req.stopSequences.length > 0) config.stopSequences = req.stopSequences;
|
|
40
|
+
const tools = buildTools(req);
|
|
41
|
+
if (tools) config.tools = tools;
|
|
42
|
+
const toolConfig = mapToolChoice(req.toolChoice);
|
|
43
|
+
if (toolConfig) config.toolConfig = toolConfig;
|
|
44
|
+
if (req.thinking === "adaptive" && spec.capabilities.thinking) {
|
|
45
|
+
config.thinkingConfig = { thinkingBudget: -1 };
|
|
46
|
+
}
|
|
47
|
+
if (req.responseFormat && spec.capabilities.structuredOutput) {
|
|
48
|
+
config.responseMimeType = "application/json";
|
|
49
|
+
config.responseSchema = req.responseFormat.schema;
|
|
50
|
+
}
|
|
51
|
+
if (req.providerExtras) Object.assign(config, req.providerExtras);
|
|
52
|
+
return { model: spec.nativeId, contents: toContents(req), config };
|
|
53
|
+
}
|
|
54
|
+
function toContents(req) {
|
|
55
|
+
return req.messages.map((m) => ({
|
|
56
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
57
|
+
parts: (typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content).map(toPart)
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
function toPart(b) {
|
|
61
|
+
switch (b.type) {
|
|
62
|
+
case "text":
|
|
63
|
+
return { text: b.text };
|
|
64
|
+
case "image":
|
|
65
|
+
if (b.source.type !== "base64") throw new CapabilityError("Google image input requires base64 data, not a URL.");
|
|
66
|
+
return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };
|
|
67
|
+
case "audio":
|
|
68
|
+
if (b.source.type !== "base64") throw new CapabilityError("Google audio input requires base64 data, not a URL.");
|
|
69
|
+
return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };
|
|
70
|
+
case "document":
|
|
71
|
+
if (b.source.type !== "base64") throw new CapabilityError("Google document input requires base64 data, not a URL.");
|
|
72
|
+
return { inlineData: { mimeType: "application/pdf", data: b.source.data } };
|
|
73
|
+
case "tool_use":
|
|
74
|
+
return { functionCall: { name: b.name, args: b.input } };
|
|
75
|
+
case "tool_result":
|
|
76
|
+
return {
|
|
77
|
+
functionResponse: {
|
|
78
|
+
// Gemini correlates by name; the canonical tool_use id is keyed to it.
|
|
79
|
+
name: b.toolUseId,
|
|
80
|
+
response: { result: typeof b.content === "string" ? b.content : stringifyBlocks(b.content) }
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
case "thinking":
|
|
84
|
+
return { text: "" };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function stringifyBlocks(blocks) {
|
|
88
|
+
return blocks.map((b) => b.type === "text" ? b.text : `[${b.type}]`).join("");
|
|
89
|
+
}
|
|
90
|
+
function buildTools(req) {
|
|
91
|
+
const tools = [];
|
|
92
|
+
if (req.tools && req.tools.length > 0) {
|
|
93
|
+
tools.push({
|
|
94
|
+
functionDeclarations: req.tools.map((t) => ({
|
|
95
|
+
name: t.name,
|
|
96
|
+
description: t.description,
|
|
97
|
+
parameters: t.inputSchema
|
|
98
|
+
}))
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (req.webSearch) tools.push({ googleSearch: {} });
|
|
102
|
+
return tools.length > 0 ? tools : void 0;
|
|
103
|
+
}
|
|
104
|
+
function mapToolChoice(tc) {
|
|
105
|
+
if (tc === void 0 || tc === "auto") return void 0;
|
|
106
|
+
if (tc === "none") return { functionCallingConfig: { mode: FunctionCallingConfigMode.NONE } };
|
|
107
|
+
if (tc === "any") return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY } };
|
|
108
|
+
return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY, allowedFunctionNames: [tc.name] } };
|
|
109
|
+
}
|
|
110
|
+
function mapResponse(res, req) {
|
|
111
|
+
const candidate = res.candidates?.[0];
|
|
112
|
+
const parts = candidate?.content?.parts ?? [];
|
|
113
|
+
const content = [];
|
|
114
|
+
let sawToolUse = false;
|
|
115
|
+
for (const p of parts) {
|
|
116
|
+
if (p.text) content.push({ type: "text", text: p.text });
|
|
117
|
+
else if (p.functionCall) {
|
|
118
|
+
sawToolUse = true;
|
|
119
|
+
const name = p.functionCall.name ?? "";
|
|
120
|
+
content.push({ type: "tool_use", id: p.functionCall.id ?? name, name, input: p.functionCall.args ?? {} });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
id: res.responseId ?? "",
|
|
125
|
+
model: req.model,
|
|
126
|
+
provider: "google",
|
|
127
|
+
role: "assistant",
|
|
128
|
+
content,
|
|
129
|
+
stopReason: sawToolUse ? "tool_use" : mapFinish(candidate?.finishReason),
|
|
130
|
+
usage: mapUsage(res)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function mapFinish(reason) {
|
|
134
|
+
switch (reason) {
|
|
135
|
+
case "STOP":
|
|
136
|
+
return "end_turn";
|
|
137
|
+
case "MAX_TOKENS":
|
|
138
|
+
return "max_tokens";
|
|
139
|
+
case "SAFETY":
|
|
140
|
+
case "RECITATION":
|
|
141
|
+
case "BLOCKLIST":
|
|
142
|
+
case "PROHIBITED_CONTENT":
|
|
143
|
+
case "SPII":
|
|
144
|
+
return "refusal";
|
|
145
|
+
default:
|
|
146
|
+
return "end_turn";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function mapUsage(res) {
|
|
150
|
+
const u = res.usageMetadata;
|
|
151
|
+
const usage = {
|
|
152
|
+
inputTokens: u?.promptTokenCount ?? 0,
|
|
153
|
+
outputTokens: u?.candidatesTokenCount ?? 0
|
|
154
|
+
};
|
|
155
|
+
if (u?.cachedContentTokenCount != null) usage.cacheReadTokens = u.cachedContentTokenCount;
|
|
156
|
+
return usage;
|
|
157
|
+
}
|
|
158
|
+
async function* mapStream(iter, req) {
|
|
159
|
+
let started = false;
|
|
160
|
+
let textOpen = false;
|
|
161
|
+
let nextBlockIndex = 1;
|
|
162
|
+
let stopReason = "end_turn";
|
|
163
|
+
const usage = emptyUsage();
|
|
164
|
+
for await (const chunk of iter) {
|
|
165
|
+
if (!started) {
|
|
166
|
+
started = true;
|
|
167
|
+
yield {
|
|
168
|
+
type: "message_start",
|
|
169
|
+
message: { id: chunk.responseId ?? "", model: req.model, provider: "google", role: "assistant", content: [], usage: emptyUsage() }
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const candidate = chunk.candidates?.[0];
|
|
173
|
+
for (const p of candidate?.content?.parts ?? []) {
|
|
174
|
+
if (p.text) {
|
|
175
|
+
if (!textOpen) {
|
|
176
|
+
textOpen = true;
|
|
177
|
+
yield { type: "content_block_start", index: 0, contentBlock: { type: "text", text: "" } };
|
|
178
|
+
}
|
|
179
|
+
yield { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: p.text } };
|
|
180
|
+
} else if (p.functionCall) {
|
|
181
|
+
const idx = nextBlockIndex++;
|
|
182
|
+
const name = p.functionCall.name ?? "";
|
|
183
|
+
yield { type: "content_block_start", index: idx, contentBlock: { type: "tool_use", id: p.functionCall.id ?? name, name, input: {} } };
|
|
184
|
+
yield { type: "content_block_delta", index: idx, delta: { type: "input_json_delta", partialJson: JSON.stringify(p.functionCall.args ?? {}) } };
|
|
185
|
+
yield { type: "content_block_stop", index: idx };
|
|
186
|
+
stopReason = "tool_use";
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (chunk.usageMetadata) {
|
|
190
|
+
const u = mapUsage(chunk);
|
|
191
|
+
usage.inputTokens = u.inputTokens;
|
|
192
|
+
usage.outputTokens = u.outputTokens;
|
|
193
|
+
if (u.cacheReadTokens != null) usage.cacheReadTokens = u.cacheReadTokens;
|
|
194
|
+
}
|
|
195
|
+
if (candidate?.finishReason && stopReason !== "tool_use") stopReason = mapFinish(candidate.finishReason);
|
|
196
|
+
}
|
|
197
|
+
if (textOpen) yield { type: "content_block_stop", index: 0 };
|
|
198
|
+
yield { type: "message_delta", delta: { stopReason }, usage };
|
|
199
|
+
yield { type: "message_stop" };
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
GoogleProvider,
|
|
203
|
+
mapResponse,
|
|
204
|
+
toContents
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=google-3TFOFIQO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/google.ts"],"sourcesContent":["// Google (Gemini) adapter, on @google/genai's models.generateContent /\n// generateContentStream. Gemini is natively multimodal (text + image + audio +\n// PDF), so most translation is structural: canonical messages → Gemini\n// `contents` (role \"assistant\" → \"model\"), tools → functionDeclarations,\n// system → config.systemInstruction, tool_use/tool_result → functionCall/\n// functionResponse (name-keyed — Gemini correlates by function name, so we key\n// the canonical tool_use id to the name).\n\nimport { GoogleGenAI, FunctionCallingConfigMode } from \"@google/genai\";\nimport type { Content, GenerateContentResponse, GenerateContentParameters, Part } from \"@google/genai\";\nimport type { ModelSpec } from \"../shape/capabilities\";\nimport type { Provider } from \"./types\";\nimport { mapProviderError, normalizeError } from \"./errors\";\nimport {\n CapabilityError,\n emptyUsage,\n type OracleContentBlock,\n type OracleEvent,\n type OracleRequest,\n type OracleResponse,\n type OracleStopReason,\n type OracleUsage,\n type ToolChoice,\n} from \"../shape/index\";\n\nexport class GoogleProvider implements Provider {\n readonly id = \"google\" as const;\n private client: GoogleGenAI;\n\n constructor(apiKey: string, client?: GoogleGenAI) {\n this.client = client ?? new GoogleGenAI({ apiKey });\n }\n\n async create(spec: ModelSpec, req: OracleRequest): Promise<OracleResponse> {\n try {\n const res = await this.client.models.generateContent(buildParams(spec, req));\n return mapResponse(res, req);\n } catch (err) {\n mapProviderError(err, \"google\");\n }\n }\n\n async *stream(spec: ModelSpec, req: OracleRequest): AsyncIterable<OracleEvent> {\n try {\n const iter = await this.client.models.generateContentStream(buildParams(spec, req));\n yield* mapStream(iter, req);\n } catch (err) {\n yield { type: \"error\", error: normalizeError(err, \"google\").toShape() };\n }\n }\n}\n\n// ----- request mapping -----\n\nfunction buildParams(spec: ModelSpec, req: OracleRequest): GenerateContentParameters {\n const config: Record<string, unknown> = { maxOutputTokens: req.maxTokens };\n\n if (req.system !== undefined) {\n config.systemInstruction = typeof req.system === \"string\" ? req.system : req.system.map((b) => b.text).join(\"\");\n }\n if (req.temperature !== undefined) config.temperature = req.temperature;\n if (req.stopSequences && req.stopSequences.length > 0) config.stopSequences = req.stopSequences;\n\n const tools = buildTools(req);\n if (tools) config.tools = tools;\n\n const toolConfig = mapToolChoice(req.toolChoice);\n if (toolConfig) config.toolConfig = toolConfig;\n\n if (req.thinking === \"adaptive\" && spec.capabilities.thinking) {\n config.thinkingConfig = { thinkingBudget: -1 }; // -1 = dynamic (\"let the model decide\")\n }\n\n if (req.responseFormat && spec.capabilities.structuredOutput) {\n config.responseMimeType = \"application/json\";\n config.responseSchema = req.responseFormat.schema;\n }\n\n if (req.providerExtras) Object.assign(config, req.providerExtras);\n\n return { model: spec.nativeId, contents: toContents(req), config } as unknown as GenerateContentParameters;\n}\n\nexport function toContents(req: OracleRequest): Content[] {\n return req.messages.map((m) => ({\n role: m.role === \"assistant\" ? \"model\" : \"user\",\n parts: (typeof m.content === \"string\" ? [{ type: \"text\" as const, text: m.content }] : m.content).map(toPart),\n }));\n}\n\nfunction toPart(b: OracleContentBlock): Part {\n switch (b.type) {\n case \"text\":\n return { text: b.text };\n case \"image\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google image input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };\n case \"audio\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google audio input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: b.source.mediaType, data: b.source.data } };\n case \"document\":\n if (b.source.type !== \"base64\") throw new CapabilityError(\"Google document input requires base64 data, not a URL.\");\n return { inlineData: { mimeType: \"application/pdf\", data: b.source.data } };\n case \"tool_use\":\n return { functionCall: { name: b.name, args: b.input } };\n case \"tool_result\":\n return {\n functionResponse: {\n // Gemini correlates by name; the canonical tool_use id is keyed to it.\n name: b.toolUseId,\n response: { result: typeof b.content === \"string\" ? b.content : stringifyBlocks(b.content) },\n },\n };\n case \"thinking\":\n return { text: \"\" }; // no thinking input channel; drop content\n }\n}\n\nfunction stringifyBlocks(blocks: OracleContentBlock[]): string {\n return blocks.map((b) => (b.type === \"text\" ? b.text : `[${b.type}]`)).join(\"\");\n}\n\nfunction buildTools(req: OracleRequest): unknown[] | undefined {\n const tools: unknown[] = [];\n if (req.tools && req.tools.length > 0) {\n tools.push({\n functionDeclarations: req.tools.map((t) => ({\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n })),\n });\n }\n if (req.webSearch) tools.push({ googleSearch: {} });\n return tools.length > 0 ? tools : undefined;\n}\n\nfunction mapToolChoice(tc: ToolChoice | undefined): unknown | undefined {\n if (tc === undefined || tc === \"auto\") return undefined;\n if (tc === \"none\") return { functionCallingConfig: { mode: FunctionCallingConfigMode.NONE } };\n if (tc === \"any\") return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY } };\n return { functionCallingConfig: { mode: FunctionCallingConfigMode.ANY, allowedFunctionNames: [tc.name] } };\n}\n\n// ----- response mapping -----\n\nexport function mapResponse(res: GenerateContentResponse, req: OracleRequest): OracleResponse {\n const candidate = res.candidates?.[0];\n const parts = candidate?.content?.parts ?? [];\n const content: OracleContentBlock[] = [];\n let sawToolUse = false;\n\n for (const p of parts) {\n if (p.text) content.push({ type: \"text\", text: p.text });\n else if (p.functionCall) {\n sawToolUse = true;\n const name = p.functionCall.name ?? \"\";\n content.push({ type: \"tool_use\", id: p.functionCall.id ?? name, name, input: (p.functionCall.args ?? {}) as Record<string, unknown> });\n }\n }\n\n return {\n id: res.responseId ?? \"\",\n model: req.model,\n provider: \"google\",\n role: \"assistant\",\n content,\n stopReason: sawToolUse ? \"tool_use\" : mapFinish(candidate?.finishReason),\n usage: mapUsage(res),\n };\n}\n\nfunction mapFinish(reason: string | null | undefined): OracleStopReason {\n switch (reason) {\n case \"STOP\":\n return \"end_turn\";\n case \"MAX_TOKENS\":\n return \"max_tokens\";\n case \"SAFETY\":\n case \"RECITATION\":\n case \"BLOCKLIST\":\n case \"PROHIBITED_CONTENT\":\n case \"SPII\":\n return \"refusal\";\n default:\n return \"end_turn\";\n }\n}\n\nfunction mapUsage(res: GenerateContentResponse): OracleUsage {\n const u = res.usageMetadata;\n const usage: OracleUsage = {\n inputTokens: u?.promptTokenCount ?? 0,\n outputTokens: u?.candidatesTokenCount ?? 0,\n };\n if (u?.cachedContentTokenCount != null) usage.cacheReadTokens = u.cachedContentTokenCount;\n return usage;\n}\n\n// ----- stream mapping -----\n\nasync function* mapStream(iter: AsyncIterable<GenerateContentResponse>, req: OracleRequest): AsyncIterable<OracleEvent> {\n let started = false;\n let textOpen = false;\n let nextBlockIndex = 1;\n let stopReason: OracleStopReason = \"end_turn\";\n const usage = emptyUsage();\n\n for await (const chunk of iter) {\n if (!started) {\n started = true;\n yield {\n type: \"message_start\",\n message: { id: chunk.responseId ?? \"\", model: req.model, provider: \"google\", role: \"assistant\", content: [], usage: emptyUsage() },\n };\n }\n\n const candidate = chunk.candidates?.[0];\n for (const p of candidate?.content?.parts ?? []) {\n if (p.text) {\n if (!textOpen) {\n textOpen = true;\n yield { type: \"content_block_start\", index: 0, contentBlock: { type: \"text\", text: \"\" } };\n }\n yield { type: \"content_block_delta\", index: 0, delta: { type: \"text_delta\", text: p.text } };\n } else if (p.functionCall) {\n const idx = nextBlockIndex++;\n const name = p.functionCall.name ?? \"\";\n yield { type: \"content_block_start\", index: idx, contentBlock: { type: \"tool_use\", id: p.functionCall.id ?? name, name, input: {} } };\n yield { type: \"content_block_delta\", index: idx, delta: { type: \"input_json_delta\", partialJson: JSON.stringify(p.functionCall.args ?? {}) } };\n yield { type: \"content_block_stop\", index: idx };\n stopReason = \"tool_use\";\n }\n }\n\n if (chunk.usageMetadata) {\n const u = mapUsage(chunk);\n usage.inputTokens = u.inputTokens; // Gemini reports cumulative counts\n usage.outputTokens = u.outputTokens;\n if (u.cacheReadTokens != null) usage.cacheReadTokens = u.cacheReadTokens;\n }\n if (candidate?.finishReason && stopReason !== \"tool_use\") stopReason = mapFinish(candidate.finishReason);\n }\n\n if (textOpen) yield { type: \"content_block_stop\", index: 0 };\n yield { type: \"message_delta\", delta: { stopReason }, usage };\n yield { type: \"message_stop\" };\n}\n"],"mappings":"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","names":[]}
|