@venturekit-pro/ai 0.0.0-dev.20260525024212 → 0.0.0-dev.20260602193331
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/chat/anthropic.d.ts +31 -0
- package/dist/chat/anthropic.d.ts.map +1 -0
- package/dist/chat/anthropic.js +134 -0
- package/dist/chat/anthropic.js.map +1 -0
- package/dist/chat/client.d.ts +53 -0
- package/dist/chat/client.d.ts.map +1 -0
- package/dist/chat/client.js +61 -0
- package/dist/chat/client.js.map +1 -0
- package/dist/chat/cost-util.d.ts +37 -0
- package/dist/chat/cost-util.d.ts.map +1 -0
- package/dist/chat/cost-util.js +50 -0
- package/dist/chat/cost-util.js.map +1 -0
- package/dist/chat/google.d.ts +31 -0
- package/dist/chat/google.d.ts.map +1 -0
- package/dist/chat/google.js +119 -0
- package/dist/chat/google.js.map +1 -0
- package/dist/chat/index.d.ts +15 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +17 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/openai.d.ts +28 -0
- package/dist/chat/openai.d.ts.map +1 -0
- package/dist/chat/openai.js +102 -0
- package/dist/chat/openai.js.map +1 -0
- package/dist/chat/types.d.ts +135 -0
- package/dist/chat/types.d.ts.map +1 -0
- package/dist/chat/types.js +14 -0
- package/dist/chat/types.js.map +1 -0
- package/dist/cost/catalog.d.ts +31 -0
- package/dist/cost/catalog.d.ts.map +1 -0
- package/dist/cost/catalog.js +43 -0
- package/dist/cost/catalog.js.map +1 -0
- package/dist/cost/estimate.d.ts +60 -0
- package/dist/cost/estimate.d.ts.map +1 -0
- package/dist/cost/estimate.js +65 -0
- package/dist/cost/estimate.js.map +1 -0
- package/dist/cost/index.d.ts +9 -0
- package/dist/cost/index.d.ts.map +1 -0
- package/dist/cost/index.js +6 -0
- package/dist/cost/index.js.map +1 -0
- package/dist/cost/types.d.ts +75 -0
- package/dist/cost/types.d.ts.map +1 -0
- package/dist/cost/types.js +22 -0
- package/dist/cost/types.js.map +1 -0
- package/dist/cost-ledger/index.d.ts +13 -0
- package/dist/cost-ledger/index.d.ts.map +1 -0
- package/dist/cost-ledger/index.js +11 -0
- package/dist/cost-ledger/index.js.map +1 -0
- package/dist/cost-ledger/migrations/vk_ai_cost_0001_init.sql +74 -0
- package/dist/cost-ledger/migrations/vk_ai_cost_0002_llm_models.sql +128 -0
- package/dist/cost-ledger/path.d.ts +10 -0
- package/dist/cost-ledger/path.d.ts.map +1 -0
- package/dist/cost-ledger/path.js +17 -0
- package/dist/cost-ledger/path.js.map +1 -0
- package/dist/cost-ledger/query.d.ts +64 -0
- package/dist/cost-ledger/query.d.ts.map +1 -0
- package/dist/cost-ledger/query.js +139 -0
- package/dist/cost-ledger/query.js.map +1 -0
- package/dist/cost-ledger/record.d.ts +14 -0
- package/dist/cost-ledger/record.d.ts.map +1 -0
- package/dist/cost-ledger/record.js +113 -0
- package/dist/cost-ledger/record.js.map +1 -0
- package/dist/cost-ledger/types.d.ts +122 -0
- package/dist/cost-ledger/types.d.ts.map +1 -0
- package/dist/cost-ledger/types.js +22 -0
- package/dist/cost-ledger/types.js.map +1 -0
- package/dist/fallback/fallback.d.ts +49 -0
- package/dist/fallback/fallback.d.ts.map +1 -0
- package/dist/fallback/fallback.js +54 -0
- package/dist/fallback/fallback.js.map +1 -0
- package/dist/fallback/index.d.ts +14 -0
- package/dist/fallback/index.d.ts.map +1 -0
- package/dist/fallback/index.js +12 -0
- package/dist/fallback/index.js.map +1 -0
- package/dist/fallback/retry.d.ts +61 -0
- package/dist/fallback/retry.d.ts.map +1 -0
- package/dist/fallback/retry.js +99 -0
- package/dist/fallback/retry.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/prompts/index.d.ts +13 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +12 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/template.d.ts +57 -0
- package/dist/prompts/template.d.ts.map +1 -0
- package/dist/prompts/template.js +119 -0
- package/dist/prompts/template.js.map +1 -0
- package/dist/prompts/types.d.ts +77 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +25 -0
- package/dist/prompts/types.js.map +1 -0
- package/package.json +8 -4
- package/src/cost-ledger/migrations/vk_ai_cost_0001_init.sql +74 -0
- package/src/cost-ledger/migrations/vk_ai_cost_0002_llm_models.sql +128 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic chat-completion adapter.
|
|
3
|
+
*
|
|
4
|
+
* Calls Anthropic's Messages REST API directly with `fetch()` rather
|
|
5
|
+
* than depending on `@anthropic-ai/sdk`. Same rationale as `openai.ts`
|
|
6
|
+
* and `google.ts`:
|
|
7
|
+
* - single-turn chat completion is a trivial REST POST; the SDK
|
|
8
|
+
* adds ~MB of streaming + tool-use plumbing this module doesn't
|
|
9
|
+
* need.
|
|
10
|
+
* - keeps the package's peer-dep surface tighter (no
|
|
11
|
+
* `@anthropic-ai/sdk` peer requirement).
|
|
12
|
+
* - retries + fallback are owned by the `fallback/` module, not
|
|
13
|
+
* the SDK.
|
|
14
|
+
*
|
|
15
|
+
* Anthropic's API splits the system prompt out of the message array
|
|
16
|
+
* (sent as a top-level `system` field) — adapter handles that
|
|
17
|
+
* translation so callers can keep using the provider-neutral
|
|
18
|
+
* `ChatMessage[]` shape.
|
|
19
|
+
*/
|
|
20
|
+
import type { ChatCompletionRequest, ChatCompletionResult } from './types.js';
|
|
21
|
+
export interface AnthropicAdapterConfig {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
/** Override the Anthropic API host. Defaults to `https://api.anthropic.com`. */
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
/** API version header. Pinned to a known-good date if omitted. */
|
|
26
|
+
apiVersion?: string;
|
|
27
|
+
/** Request timeout in milliseconds. Default 60s. */
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function completeWithAnthropic(config: AnthropicAdapterConfig, request: ChatCompletionRequest): Promise<ChatCompletionResult>;
|
|
31
|
+
//# sourceMappingURL=anthropic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA+E/B"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic chat-completion adapter.
|
|
3
|
+
*
|
|
4
|
+
* Calls Anthropic's Messages REST API directly with `fetch()` rather
|
|
5
|
+
* than depending on `@anthropic-ai/sdk`. Same rationale as `openai.ts`
|
|
6
|
+
* and `google.ts`:
|
|
7
|
+
* - single-turn chat completion is a trivial REST POST; the SDK
|
|
8
|
+
* adds ~MB of streaming + tool-use plumbing this module doesn't
|
|
9
|
+
* need.
|
|
10
|
+
* - keeps the package's peer-dep surface tighter (no
|
|
11
|
+
* `@anthropic-ai/sdk` peer requirement).
|
|
12
|
+
* - retries + fallback are owned by the `fallback/` module, not
|
|
13
|
+
* the SDK.
|
|
14
|
+
*
|
|
15
|
+
* Anthropic's API splits the system prompt out of the message array
|
|
16
|
+
* (sent as a top-level `system` field) — adapter handles that
|
|
17
|
+
* translation so callers can keep using the provider-neutral
|
|
18
|
+
* `ChatMessage[]` shape.
|
|
19
|
+
*/
|
|
20
|
+
import { stampCost } from './cost-util.js';
|
|
21
|
+
/** Default `anthropic-version` header. Bump alongside SDK pinning. */
|
|
22
|
+
const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
|
|
23
|
+
export async function completeWithAnthropic(config, request) {
|
|
24
|
+
const startedAt = Date.now();
|
|
25
|
+
const baseUrl = (config.baseUrl ?? 'https://api.anthropic.com').replace(/\/+$/, '');
|
|
26
|
+
const apiVersion = config.apiVersion ?? DEFAULT_ANTHROPIC_VERSION;
|
|
27
|
+
const timeoutMs = request.timeoutMs ?? config.timeoutMs ?? 60_000;
|
|
28
|
+
// Anthropic puts `system` outside the messages array. Pull the
|
|
29
|
+
// first message off if it's a system; reject any later system
|
|
30
|
+
// messages (Anthropic doesn't accept multiple).
|
|
31
|
+
const { system, conversation } = splitSystem(request.messages);
|
|
32
|
+
const body = {
|
|
33
|
+
model: request.model,
|
|
34
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
35
|
+
messages: conversation.map((m) => ({ role: m.role, content: m.content })),
|
|
36
|
+
};
|
|
37
|
+
if (request.temperature !== undefined)
|
|
38
|
+
body.temperature = request.temperature;
|
|
39
|
+
if (request.topP !== undefined)
|
|
40
|
+
body.top_p = request.topP;
|
|
41
|
+
if (request.stop && request.stop.length > 0)
|
|
42
|
+
body.stop_sequences = request.stop;
|
|
43
|
+
if (system !== undefined)
|
|
44
|
+
body.system = system;
|
|
45
|
+
const controller = new AbortController();
|
|
46
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await fetch(`${baseUrl}/v1/messages`, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'x-api-key': config.apiKey,
|
|
53
|
+
'anthropic-version': apiVersion,
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
...(request.idempotencyKey ? { 'Idempotency-Key': request.idempotencyKey } : {}),
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(body),
|
|
58
|
+
signal: controller.signal,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const text = (await response.text()).slice(0, 500);
|
|
66
|
+
throw new Error(`[ai/chat/anthropic] ${response.status} ${response.statusText}: ${text}`);
|
|
67
|
+
}
|
|
68
|
+
const json = (await response.json());
|
|
69
|
+
// Anthropic returns a `content` array of blocks; we concatenate
|
|
70
|
+
// all `text` blocks. Tool-use blocks pass through silently (this
|
|
71
|
+
// module doesn't support tools — if a caller routes a tool-using
|
|
72
|
+
// prompt here, they'll see an empty completion and our error
|
|
73
|
+
// below will fire).
|
|
74
|
+
const output = (json.content ?? [])
|
|
75
|
+
.filter((b) => b.type === 'text' && typeof b.text === 'string')
|
|
76
|
+
.map((b) => b.text)
|
|
77
|
+
.join('');
|
|
78
|
+
if (output.length === 0) {
|
|
79
|
+
throw new Error(`[ai/chat/anthropic] Empty completion (stop_reason=${json.stop_reason ?? 'unknown'})`);
|
|
80
|
+
}
|
|
81
|
+
const usage = {
|
|
82
|
+
tokensIn: json.usage?.input_tokens ?? null,
|
|
83
|
+
tokensOut: json.usage?.output_tokens ?? null,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
output,
|
|
87
|
+
provider: 'anthropic',
|
|
88
|
+
model: request.model,
|
|
89
|
+
usage,
|
|
90
|
+
cost: stampCost(usage, request.pricing),
|
|
91
|
+
latencyMs: Date.now() - startedAt,
|
|
92
|
+
finishReason: mapFinishReason(json.stop_reason ?? null),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Split a leading `system` message off the conversation. Anthropic
|
|
97
|
+
* rejects multiple system messages with HTTP 400 — surface that
|
|
98
|
+
* up-front as a clearer client-side error.
|
|
99
|
+
*/
|
|
100
|
+
function splitSystem(messages) {
|
|
101
|
+
let system;
|
|
102
|
+
const conversation = [];
|
|
103
|
+
for (const m of messages) {
|
|
104
|
+
if (m.role === 'system') {
|
|
105
|
+
if (system !== undefined || conversation.length > 0) {
|
|
106
|
+
throw new Error('[ai/chat/anthropic] Only a single leading `system` message is allowed');
|
|
107
|
+
}
|
|
108
|
+
system = m.content;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
conversation.push({ role: m.role, content: m.content });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (conversation.length === 0) {
|
|
115
|
+
throw new Error('[ai/chat/anthropic] No `user` or `assistant` messages supplied');
|
|
116
|
+
}
|
|
117
|
+
return { system, conversation };
|
|
118
|
+
}
|
|
119
|
+
function mapFinishReason(raw) {
|
|
120
|
+
if (!raw)
|
|
121
|
+
return null;
|
|
122
|
+
switch (raw) {
|
|
123
|
+
case 'end_turn':
|
|
124
|
+
case 'stop_sequence':
|
|
125
|
+
return 'stop';
|
|
126
|
+
case 'max_tokens':
|
|
127
|
+
return 'length';
|
|
128
|
+
case 'tool_use':
|
|
129
|
+
return 'tool_calls';
|
|
130
|
+
default:
|
|
131
|
+
return raw;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=anthropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/chat/anthropic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat completion client — provider-neutral entry point.
|
|
3
|
+
*
|
|
4
|
+
* The CMS (or any other caller) constructs one client per
|
|
5
|
+
* (provider, apiKey) pair via `createChatClient()` and calls
|
|
6
|
+
* `.complete()` against it. The factory switches on `provider`
|
|
7
|
+
* exactly once, at construction — no per-call branch.
|
|
8
|
+
*
|
|
9
|
+
* Why a class-shaped client rather than a stateless function?
|
|
10
|
+
* - Lets adapters keep SDK instances warm (Anthropic's SDK
|
|
11
|
+
* constructs an axios instance internally; reconstructing per
|
|
12
|
+
* call would erase its connection-pool benefit).
|
|
13
|
+
* - Gives `fallback/withFallback()` and other higher-order
|
|
14
|
+
* orchestrators a stable handle to bind to.
|
|
15
|
+
*
|
|
16
|
+
* The interface deliberately matches what the CMS's `persona.ts`
|
|
17
|
+
* needs and nothing else: `provider` for audit-row labeling,
|
|
18
|
+
* `complete()` for the actual call. Tool use, streaming, batching,
|
|
19
|
+
* and embedding lookups are out of scope here and live in their
|
|
20
|
+
* dedicated modules.
|
|
21
|
+
*/
|
|
22
|
+
import type { ChatCompletionRequest, ChatCompletionResult, ChatProvider } from './types.js';
|
|
23
|
+
import { type AnthropicAdapterConfig } from './anthropic.js';
|
|
24
|
+
import { type OpenAiAdapterConfig } from './openai.js';
|
|
25
|
+
import { type GoogleAdapterConfig } from './google.js';
|
|
26
|
+
/**
|
|
27
|
+
* A minimal interface for chat completion. Implementations are
|
|
28
|
+
* per-provider thin wrappers that hold the API key + base URL +
|
|
29
|
+
* timeout and translate to the provider's REST or SDK shape.
|
|
30
|
+
*/
|
|
31
|
+
export interface ChatCompletionClient {
|
|
32
|
+
readonly provider: ChatProvider;
|
|
33
|
+
complete(request: ChatCompletionRequest): Promise<ChatCompletionResult>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Construct-time configuration. The provider-specific fields are
|
|
37
|
+
* carried as a union discriminated by `provider` so TypeScript can
|
|
38
|
+
* narrow `baseUrl` / `apiVersion` to only the providers that accept
|
|
39
|
+
* them.
|
|
40
|
+
*/
|
|
41
|
+
export type ChatClientConfig = ({
|
|
42
|
+
provider: 'anthropic';
|
|
43
|
+
} & AnthropicAdapterConfig) | ({
|
|
44
|
+
provider: 'openai';
|
|
45
|
+
} & OpenAiAdapterConfig) | ({
|
|
46
|
+
provider: 'google';
|
|
47
|
+
} & GoogleAdapterConfig);
|
|
48
|
+
/**
|
|
49
|
+
* Build a client. Throws synchronously on an unknown provider so the
|
|
50
|
+
* caller fails fast rather than at the first `.complete()` call.
|
|
51
|
+
*/
|
|
52
|
+
export declare function createChatClient(config: ChatClientConfig): ChatCompletionClient;
|
|
53
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/chat/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EAAyB,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE3E;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACzE;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB,CAAC;IAAE,QAAQ,EAAE,WAAW,CAAA;CAAE,GAAG,sBAAsB,CAAC,GACpD,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAAG,mBAAmB,CAAC,GAC9C,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAAG,mBAAmB,CAAC,CAAC;AAEnD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,oBAAoB,CAiC/E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat completion client — provider-neutral entry point.
|
|
3
|
+
*
|
|
4
|
+
* The CMS (or any other caller) constructs one client per
|
|
5
|
+
* (provider, apiKey) pair via `createChatClient()` and calls
|
|
6
|
+
* `.complete()` against it. The factory switches on `provider`
|
|
7
|
+
* exactly once, at construction — no per-call branch.
|
|
8
|
+
*
|
|
9
|
+
* Why a class-shaped client rather than a stateless function?
|
|
10
|
+
* - Lets adapters keep SDK instances warm (Anthropic's SDK
|
|
11
|
+
* constructs an axios instance internally; reconstructing per
|
|
12
|
+
* call would erase its connection-pool benefit).
|
|
13
|
+
* - Gives `fallback/withFallback()` and other higher-order
|
|
14
|
+
* orchestrators a stable handle to bind to.
|
|
15
|
+
*
|
|
16
|
+
* The interface deliberately matches what the CMS's `persona.ts`
|
|
17
|
+
* needs and nothing else: `provider` for audit-row labeling,
|
|
18
|
+
* `complete()` for the actual call. Tool use, streaming, batching,
|
|
19
|
+
* and embedding lookups are out of scope here and live in their
|
|
20
|
+
* dedicated modules.
|
|
21
|
+
*/
|
|
22
|
+
import { completeWithAnthropic } from './anthropic.js';
|
|
23
|
+
import { completeWithOpenAi } from './openai.js';
|
|
24
|
+
import { completeWithGoogle } from './google.js';
|
|
25
|
+
/**
|
|
26
|
+
* Build a client. Throws synchronously on an unknown provider so the
|
|
27
|
+
* caller fails fast rather than at the first `.complete()` call.
|
|
28
|
+
*/
|
|
29
|
+
export function createChatClient(config) {
|
|
30
|
+
switch (config.provider) {
|
|
31
|
+
case 'anthropic': {
|
|
32
|
+
const { provider: _ignored, ...adapterConfig } = config;
|
|
33
|
+
void _ignored;
|
|
34
|
+
return {
|
|
35
|
+
provider: 'anthropic',
|
|
36
|
+
complete: (request) => completeWithAnthropic(adapterConfig, request),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
case 'openai': {
|
|
40
|
+
const { provider: _ignored, ...adapterConfig } = config;
|
|
41
|
+
void _ignored;
|
|
42
|
+
return {
|
|
43
|
+
provider: 'openai',
|
|
44
|
+
complete: (request) => completeWithOpenAi(adapterConfig, request),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
case 'google': {
|
|
48
|
+
const { provider: _ignored, ...adapterConfig } = config;
|
|
49
|
+
void _ignored;
|
|
50
|
+
return {
|
|
51
|
+
provider: 'google',
|
|
52
|
+
complete: (request) => completeWithGoogle(adapterConfig, request),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
const _exhaustive = config;
|
|
57
|
+
throw new Error(`[ai/chat] Unsupported provider: ${_exhaustive.provider}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/chat/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,EAAE,qBAAqB,EAA+B,MAAM,gBAAgB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAA4B,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAA4B,MAAM,aAAa,CAAC;AAuB3E;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACvD,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC;YACxD,KAAK,QAAQ,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,WAAW;gBACrB,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,CAAC,aAAa,EAAE,OAAO,CAAC;aACrE,CAAC;QACJ,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC;YACxD,KAAK,QAAQ,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,aAAa,EAAE,OAAO,CAAC;aAClE,CAAC;QACJ,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,aAAa,EAAE,GAAG,MAAM,CAAC;YACxD,KAAK,QAAQ,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,QAAQ;gBAClB,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,aAAa,EAAE,OAAO,CAAC;aAClE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,MAAM,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,mCAAoC,WAAoC,CAAC,QAAQ,EAAE,CACpF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost-stamping helper shared by chat adapters.
|
|
3
|
+
*
|
|
4
|
+
* Pulled into its own module (rather than living in `cost/`) because
|
|
5
|
+
* every provider adapter needs it and we don't want to import the
|
|
6
|
+
* full `cost/` barrel — that would introduce a circular reference
|
|
7
|
+
* once `cost/` learns to consume `chat/types.ts`.
|
|
8
|
+
*
|
|
9
|
+
* The math is intentionally trivial and lives close to the adapters
|
|
10
|
+
* so reviewers don't have to jump between modules to audit pricing:
|
|
11
|
+
* `(tokensIn / 1000) * costPer1kInputUsd +
|
|
12
|
+
* (tokensOut / 1000) * costPer1kOutputUsd`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChatModelPricing, ChatTokenUsage, CostStamp } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Compute a USD `CostStamp` from raw usage counts + pricing.
|
|
17
|
+
*
|
|
18
|
+
* Returns `null` when:
|
|
19
|
+
* - `pricing` is omitted (caller didn't opt into cost stamping), OR
|
|
20
|
+
* - both `tokensIn` and `tokensOut` are `null` (vendor didn't report usage —
|
|
21
|
+
* we don't fake costs from string-length heuristics here; that's
|
|
22
|
+
* the `cost/estimate.ts` module's job, used by callers explicitly).
|
|
23
|
+
*
|
|
24
|
+
* If only one of `tokensIn` / `tokensOut` is `null`, the missing leg
|
|
25
|
+
* counts as zero — partial usage data is still better than no cost
|
|
26
|
+
* stamp, and audit views typically alarm on the absent leg rather
|
|
27
|
+
* than dropping the row.
|
|
28
|
+
*/
|
|
29
|
+
export declare function stampCost(usage: ChatTokenUsage, pricing: ChatModelPricing | undefined): CostStamp | null;
|
|
30
|
+
/**
|
|
31
|
+
* Round to 8 decimal places — enough for sub-cent precision on a
|
|
32
|
+
* single 4096-token call, not so much that JSON serialization gets
|
|
33
|
+
* noisy. Public so unit tests can use the same rounding when
|
|
34
|
+
* computing fixtures.
|
|
35
|
+
*/
|
|
36
|
+
export declare function round8(n: number): number;
|
|
37
|
+
//# sourceMappingURL=cost-util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-util.d.ts","sourceRoot":"","sources":["../../src/chat/cost-util.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE9E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,SAAS,GAAG,IAAI,CAWlB;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost-stamping helper shared by chat adapters.
|
|
3
|
+
*
|
|
4
|
+
* Pulled into its own module (rather than living in `cost/`) because
|
|
5
|
+
* every provider adapter needs it and we don't want to import the
|
|
6
|
+
* full `cost/` barrel — that would introduce a circular reference
|
|
7
|
+
* once `cost/` learns to consume `chat/types.ts`.
|
|
8
|
+
*
|
|
9
|
+
* The math is intentionally trivial and lives close to the adapters
|
|
10
|
+
* so reviewers don't have to jump between modules to audit pricing:
|
|
11
|
+
* `(tokensIn / 1000) * costPer1kInputUsd +
|
|
12
|
+
* (tokensOut / 1000) * costPer1kOutputUsd`.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Compute a USD `CostStamp` from raw usage counts + pricing.
|
|
16
|
+
*
|
|
17
|
+
* Returns `null` when:
|
|
18
|
+
* - `pricing` is omitted (caller didn't opt into cost stamping), OR
|
|
19
|
+
* - both `tokensIn` and `tokensOut` are `null` (vendor didn't report usage —
|
|
20
|
+
* we don't fake costs from string-length heuristics here; that's
|
|
21
|
+
* the `cost/estimate.ts` module's job, used by callers explicitly).
|
|
22
|
+
*
|
|
23
|
+
* If only one of `tokensIn` / `tokensOut` is `null`, the missing leg
|
|
24
|
+
* counts as zero — partial usage data is still better than no cost
|
|
25
|
+
* stamp, and audit views typically alarm on the absent leg rather
|
|
26
|
+
* than dropping the row.
|
|
27
|
+
*/
|
|
28
|
+
export function stampCost(usage, pricing) {
|
|
29
|
+
if (!pricing)
|
|
30
|
+
return null;
|
|
31
|
+
if (usage.tokensIn === null && usage.tokensOut === null)
|
|
32
|
+
return null;
|
|
33
|
+
const inputCostUsd = ((usage.tokensIn ?? 0) / 1000) * pricing.costPer1kInputUsd;
|
|
34
|
+
const outputCostUsd = ((usage.tokensOut ?? 0) / 1000) * pricing.costPer1kOutputUsd;
|
|
35
|
+
const costUsd = round8(inputCostUsd + outputCostUsd);
|
|
36
|
+
// Integer micro-USD for SQL SUM() without float drift. Round
|
|
37
|
+
// half-up at the ULP because audit rows accumulate.
|
|
38
|
+
const costMicroUsd = Math.round(costUsd * 1_000_000);
|
|
39
|
+
return { costUsd, costMicroUsd, pricing: { ...pricing } };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Round to 8 decimal places — enough for sub-cent precision on a
|
|
43
|
+
* single 4096-token call, not so much that JSON serialization gets
|
|
44
|
+
* noisy. Public so unit tests can use the same rounding when
|
|
45
|
+
* computing fixtures.
|
|
46
|
+
*/
|
|
47
|
+
export function round8(n) {
|
|
48
|
+
return Math.round(n * 1e8) / 1e8;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=cost-util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-util.js","sourceRoot":"","sources":["../../src/chat/cost-util.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CACvB,KAAqB,EACrB,OAAqC;IAErC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAErE,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAChF,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,kBAAkB,CAAC;IACnF,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC;IACrD,6DAA6D;IAC7D,oDAAoD;IACpD,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACrD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google (Gemini) chat-completion adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implemented with `fetch()` against the public REST endpoint. The
|
|
5
|
+
* `@google/generative-ai` SDK exists but adds ~300KB and pins on a
|
|
6
|
+
* Node version we'd rather not constrain; for single-turn chat the
|
|
7
|
+
* REST surface is simple enough to call directly.
|
|
8
|
+
*
|
|
9
|
+
* Notable Gemini quirks the adapter translates:
|
|
10
|
+
* - The system prompt is a separate top-level `systemInstruction`
|
|
11
|
+
* object, not a `role: 'system'` message.
|
|
12
|
+
* - Conversation roles are `'user'` and `'model'` (not `'assistant'`).
|
|
13
|
+
* - `Content` blocks wrap a `parts: [{ text: string }]` array.
|
|
14
|
+
* - Generation params live under `generationConfig.*` rather than
|
|
15
|
+
* being siblings of `model` / `messages`.
|
|
16
|
+
* - Gemini doesn't natively support an Idempotency-Key header, so
|
|
17
|
+
* `request.idempotencyKey` is ignored (documented behavior in
|
|
18
|
+
* `chat/types.ts`).
|
|
19
|
+
*/
|
|
20
|
+
import type { ChatCompletionRequest, ChatCompletionResult } from './types.js';
|
|
21
|
+
export interface GoogleAdapterConfig {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
/** Override the API host. Defaults to `https://generativelanguage.googleapis.com`. */
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
/** API version. Defaults to `v1beta` (still the recommended path for Gemini 1.5+). */
|
|
26
|
+
apiVersion?: string;
|
|
27
|
+
/** Request timeout in milliseconds. Default 60s. */
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function completeWithGoogle(config: GoogleAdapterConfig, request: ChatCompletionRequest): Promise<ChatCompletionResult>;
|
|
31
|
+
//# sourceMappingURL=google.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.d.ts","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA6E/B"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google (Gemini) chat-completion adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implemented with `fetch()` against the public REST endpoint. The
|
|
5
|
+
* `@google/generative-ai` SDK exists but adds ~300KB and pins on a
|
|
6
|
+
* Node version we'd rather not constrain; for single-turn chat the
|
|
7
|
+
* REST surface is simple enough to call directly.
|
|
8
|
+
*
|
|
9
|
+
* Notable Gemini quirks the adapter translates:
|
|
10
|
+
* - The system prompt is a separate top-level `systemInstruction`
|
|
11
|
+
* object, not a `role: 'system'` message.
|
|
12
|
+
* - Conversation roles are `'user'` and `'model'` (not `'assistant'`).
|
|
13
|
+
* - `Content` blocks wrap a `parts: [{ text: string }]` array.
|
|
14
|
+
* - Generation params live under `generationConfig.*` rather than
|
|
15
|
+
* being siblings of `model` / `messages`.
|
|
16
|
+
* - Gemini doesn't natively support an Idempotency-Key header, so
|
|
17
|
+
* `request.idempotencyKey` is ignored (documented behavior in
|
|
18
|
+
* `chat/types.ts`).
|
|
19
|
+
*/
|
|
20
|
+
import { stampCost } from './cost-util.js';
|
|
21
|
+
export async function completeWithGoogle(config, request) {
|
|
22
|
+
const startedAt = Date.now();
|
|
23
|
+
const baseUrl = (config.baseUrl ?? 'https://generativelanguage.googleapis.com').replace(/\/+$/, '');
|
|
24
|
+
const apiVersion = config.apiVersion ?? 'v1beta';
|
|
25
|
+
const timeoutMs = request.timeoutMs ?? config.timeoutMs ?? 60_000;
|
|
26
|
+
const { system, conversation } = splitSystem(request.messages);
|
|
27
|
+
const body = {
|
|
28
|
+
contents: conversation.map((m) => ({
|
|
29
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
30
|
+
parts: [{ text: m.content }],
|
|
31
|
+
})),
|
|
32
|
+
generationConfig: {
|
|
33
|
+
maxOutputTokens: request.maxTokens ?? 4096,
|
|
34
|
+
...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
|
|
35
|
+
...(request.topP !== undefined ? { topP: request.topP } : {}),
|
|
36
|
+
...(request.stop && request.stop.length > 0 ? { stopSequences: request.stop } : {}),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
if (system) {
|
|
40
|
+
body.systemInstruction = { parts: [{ text: system }] };
|
|
41
|
+
}
|
|
42
|
+
// Gemini's API-key auth uses a query param (no header form for the
|
|
43
|
+
// REST endpoint that's actually documented). We escape via
|
|
44
|
+
// encodeURIComponent so a future tenant-keyed format doesn't break
|
|
45
|
+
// the URL.
|
|
46
|
+
const url = `${baseUrl}/${apiVersion}/models/${encodeURIComponent(request.model)}:generateContent?key=${encodeURIComponent(config.apiKey)}`;
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await fetch(url, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify(body),
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const text = (await response.text()).slice(0, 500);
|
|
63
|
+
throw new Error(`[ai/chat/google] ${response.status} ${response.statusText}: ${text}`);
|
|
64
|
+
}
|
|
65
|
+
const json = (await response.json());
|
|
66
|
+
const candidate = json.candidates?.[0];
|
|
67
|
+
const text = candidate?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';
|
|
68
|
+
if (text.length === 0) {
|
|
69
|
+
throw new Error(`[ai/chat/google] Empty completion (finishReason=${candidate?.finishReason ?? 'unknown'})`);
|
|
70
|
+
}
|
|
71
|
+
const usage = {
|
|
72
|
+
tokensIn: json.usageMetadata?.promptTokenCount ?? null,
|
|
73
|
+
tokensOut: json.usageMetadata?.candidatesTokenCount ?? null,
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
output: text,
|
|
77
|
+
provider: 'google',
|
|
78
|
+
model: request.model,
|
|
79
|
+
usage,
|
|
80
|
+
cost: stampCost(usage, request.pricing),
|
|
81
|
+
latencyMs: Date.now() - startedAt,
|
|
82
|
+
finishReason: mapFinishReason(candidate?.finishReason ?? null),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function splitSystem(messages) {
|
|
86
|
+
let system;
|
|
87
|
+
const conversation = [];
|
|
88
|
+
for (const m of messages) {
|
|
89
|
+
if (m.role === 'system') {
|
|
90
|
+
if (system !== undefined || conversation.length > 0) {
|
|
91
|
+
throw new Error('[ai/chat/google] Only a single leading `system` message is allowed');
|
|
92
|
+
}
|
|
93
|
+
system = m.content;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
conversation.push(m);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (conversation.length === 0) {
|
|
100
|
+
throw new Error('[ai/chat/google] No `user` or `assistant` messages supplied');
|
|
101
|
+
}
|
|
102
|
+
return { system, conversation };
|
|
103
|
+
}
|
|
104
|
+
function mapFinishReason(raw) {
|
|
105
|
+
if (!raw)
|
|
106
|
+
return null;
|
|
107
|
+
switch (raw) {
|
|
108
|
+
case 'STOP':
|
|
109
|
+
return 'stop';
|
|
110
|
+
case 'MAX_TOKENS':
|
|
111
|
+
return 'length';
|
|
112
|
+
case 'SAFETY':
|
|
113
|
+
case 'RECITATION':
|
|
114
|
+
return 'content_filter';
|
|
115
|
+
default:
|
|
116
|
+
return raw;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"google.js","sourceRoot":"","sources":["../../src/chat/google.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat completion — provider-neutral single-turn or multi-turn text
|
|
3
|
+
* generation with cost stamping. Tool use lives in `agents/`.
|
|
4
|
+
*/
|
|
5
|
+
export type { ChatProvider, ChatMessage, ChatModelPricing, ChatCompletionRequest, ChatCompletionResult, ChatTokenUsage, ChatFinishReason, CostStamp, } from './types.js';
|
|
6
|
+
export { createChatClient } from './client.js';
|
|
7
|
+
export type { ChatClientConfig, ChatCompletionClient } from './client.js';
|
|
8
|
+
export { completeWithAnthropic } from './anthropic.js';
|
|
9
|
+
export type { AnthropicAdapterConfig } from './anthropic.js';
|
|
10
|
+
export { completeWithOpenAi } from './openai.js';
|
|
11
|
+
export type { OpenAiAdapterConfig } from './openai.js';
|
|
12
|
+
export { completeWithGoogle } from './google.js';
|
|
13
|
+
export type { GoogleAdapterConfig } from './google.js';
|
|
14
|
+
export { stampCost, round8 } from './cost-util.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,SAAS,GACV,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAM1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKvD,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat completion — provider-neutral single-turn or multi-turn text
|
|
3
|
+
* generation with cost stamping. Tool use lives in `agents/`.
|
|
4
|
+
*/
|
|
5
|
+
export { createChatClient } from './client.js';
|
|
6
|
+
// Provider adapters are exported as named functions for callers that
|
|
7
|
+
// want to bypass the factory (e.g. for a custom retry policy bound
|
|
8
|
+
// directly to one provider). Re-exported configs let those callers
|
|
9
|
+
// share the same type for `apiKey` / `baseUrl`.
|
|
10
|
+
export { completeWithAnthropic } from './anthropic.js';
|
|
11
|
+
export { completeWithOpenAi } from './openai.js';
|
|
12
|
+
export { completeWithGoogle } from './google.js';
|
|
13
|
+
// `stampCost` is exposed for callers that drive the chat call
|
|
14
|
+
// themselves (e.g. through `agents/` with tool-use) but still want
|
|
15
|
+
// the standard cost-stamp computation.
|
|
16
|
+
export { stampCost, round8 } from './cost-util.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/chat/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,qEAAqE;AACrE,mEAAmE;AACnE,mEAAmE;AACnE,gDAAgD;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,8DAA8D;AAC9D,mEAAmE;AACnE,uCAAuC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI chat-completion adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implemented with `fetch()` rather than the `openai` SDK on purpose:
|
|
5
|
+
* - keeps the package's peer-dep surface optional (`openai` is
|
|
6
|
+
* already declared in `package.json` for the agents/ tool-use
|
|
7
|
+
* path, which still uses the SDK because tool-use streaming is
|
|
8
|
+
* painful to write by hand).
|
|
9
|
+
* - single-turn chat-completion is a trivial REST POST; vendoring
|
|
10
|
+
* in the SDK just for this would add ~1MB to the bundle.
|
|
11
|
+
* - the SDK pins `Node>=18` and uses native `AbortSignal`/`fetch`
|
|
12
|
+
* under the hood anyway, so we lose nothing by going direct.
|
|
13
|
+
*
|
|
14
|
+
* Uses the Chat Completions endpoint (`/v1/chat/completions`) — not
|
|
15
|
+
* the newer Responses API — because Responses doesn't expose
|
|
16
|
+
* `max_completion_tokens` the same way and the migration story is
|
|
17
|
+
* still unsettled at v1 ship-time.
|
|
18
|
+
*/
|
|
19
|
+
import type { ChatCompletionRequest, ChatCompletionResult } from './types.js';
|
|
20
|
+
export interface OpenAiAdapterConfig {
|
|
21
|
+
apiKey: string;
|
|
22
|
+
/** Override the OpenAI API host. Defaults to `https://api.openai.com`. */
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/** Request timeout in milliseconds. Default 60s. */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function completeWithOpenAi(config: OpenAiAdapterConfig, request: ChatCompletionRequest): Promise<ChatCompletionResult>;
|
|
28
|
+
//# sourceMappingURL=openai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../src/chat/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAaD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAkE/B"}
|