@vincemakes/kiso-provider-anthropic 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/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +289 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kiso contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @vincemakes/kiso-provider-anthropic
|
|
2
|
+
|
|
3
|
+
The Anthropic adapter on the official SDK: wire events mapped to the
|
|
4
|
+
kiso event union, cancellation via the run signal, exhaustive stop-reason
|
|
5
|
+
mapping (refusal/pause_turn/context-window are never completed), real
|
|
6
|
+
usage and cache counters.
|
|
7
|
+
|
|
8
|
+
Requires Node >= 22. See the repository README for the framework
|
|
9
|
+
overview.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic adapter — built on the official SDK, mapping its wire events to
|
|
3
|
+
* the kiso event union. The kernel never sees Anthropic types (ADR-0001);
|
|
4
|
+
* everything provider-private (cache_control, thinking deltas) is digested
|
|
5
|
+
* here.
|
|
6
|
+
*
|
|
7
|
+
* Invariants this adapter upholds:
|
|
8
|
+
* - at least one `usage` precedes the final `stop` (ADR-0003);
|
|
9
|
+
* - `tool_call_end.input` is the parsed JSON or null — never a partial.
|
|
10
|
+
*
|
|
11
|
+
* Tool calls: the SDK streams tool_use as content blocks
|
|
12
|
+
* (content_block_start with the call id, input_json_delta, then stop).
|
|
13
|
+
* The id in the start block IS the callId the kernel echoes back.
|
|
14
|
+
*/
|
|
15
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
16
|
+
import type { Adapter } from "@vincemakes/kiso-core";
|
|
17
|
+
/** Config accepted by the high-level factory (七: the provider owns its SDK). */
|
|
18
|
+
export interface AnthropicProviderConfig {
|
|
19
|
+
readonly apiKey?: string;
|
|
20
|
+
readonly baseUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* High-level factory (七): builds the adapter FROM CONFIG, owning the SDK
|
|
24
|
+
* inside this package. Consumers (and the runtime's lazy provider path)
|
|
25
|
+
* import ONLY @vincemakes/kiso-provider-anthropic — the SDK stays a private
|
|
26
|
+
* dependency of this package, so nested installs resolve it next to here,
|
|
27
|
+
* never through a hoisted root.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createAnthropicProvider(config?: AnthropicProviderConfig): Adapter;
|
|
30
|
+
export declare function createAnthropicAdapter(client: Anthropic): Adapter;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic adapter — built on the official SDK, mapping its wire events to
|
|
3
|
+
* the kiso event union. The kernel never sees Anthropic types (ADR-0001);
|
|
4
|
+
* everything provider-private (cache_control, thinking deltas) is digested
|
|
5
|
+
* here.
|
|
6
|
+
*
|
|
7
|
+
* Invariants this adapter upholds:
|
|
8
|
+
* - at least one `usage` precedes the final `stop` (ADR-0003);
|
|
9
|
+
* - `tool_call_end.input` is the parsed JSON or null — never a partial.
|
|
10
|
+
*
|
|
11
|
+
* Tool calls: the SDK streams tool_use as content blocks
|
|
12
|
+
* (content_block_start with the call id, input_json_delta, then stop).
|
|
13
|
+
* The id in the start block IS the callId the kernel echoes back.
|
|
14
|
+
*/
|
|
15
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
16
|
+
import { mapApiError } from "@vincemakes/kiso-core";
|
|
17
|
+
/**
|
|
18
|
+
* High-level factory (七): builds the adapter FROM CONFIG, owning the SDK
|
|
19
|
+
* inside this package. Consumers (and the runtime's lazy provider path)
|
|
20
|
+
* import ONLY @vincemakes/kiso-provider-anthropic — the SDK stays a private
|
|
21
|
+
* dependency of this package, so nested installs resolve it next to here,
|
|
22
|
+
* never through a hoisted root.
|
|
23
|
+
*/
|
|
24
|
+
export function createAnthropicProvider(config = {}) {
|
|
25
|
+
const client = new Anthropic({
|
|
26
|
+
...(config.apiKey !== undefined ? { apiKey: config.apiKey } : {}),
|
|
27
|
+
...(config.baseUrl !== undefined ? { baseURL: config.baseUrl } : {}),
|
|
28
|
+
});
|
|
29
|
+
return createAnthropicAdapter(client);
|
|
30
|
+
}
|
|
31
|
+
export function createAnthropicAdapter(client) {
|
|
32
|
+
return {
|
|
33
|
+
async *stream(options) {
|
|
34
|
+
// D4: the stream CREATION is inside the error normalization —
|
|
35
|
+
// connection/timeout/5xx failures before the first byte are
|
|
36
|
+
// mapped, retryable StructuredErrors.
|
|
37
|
+
let stream;
|
|
38
|
+
try {
|
|
39
|
+
stream = client.messages.stream({
|
|
40
|
+
model: options.model,
|
|
41
|
+
...(options.systemPrompt !== undefined ? { system: options.systemPrompt } : {}),
|
|
42
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
43
|
+
messages: toAnthropicMessages(options.messages),
|
|
44
|
+
...(options.tools?.length ? { tools: toAnthropicTools(options.tools) } : {}),
|
|
45
|
+
...(options.temperature !== undefined ? { temperature: options.temperature } : {}),
|
|
46
|
+
},
|
|
47
|
+
// Phase B: cancellation reaches the SDK, which aborts the fetch.
|
|
48
|
+
options.signal !== undefined ? { signal: options.signal } : undefined);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
throw toAnthropicError(err);
|
|
52
|
+
}
|
|
53
|
+
let inputTokens = null;
|
|
54
|
+
let outputTokens = null;
|
|
55
|
+
let cacheRead = null;
|
|
56
|
+
let cacheWrite = null;
|
|
57
|
+
// 九: "usage SEEN" (data arrived) is distinct from "usage YIELDED"
|
|
58
|
+
// (an event left the adapter). message_start may report usage with
|
|
59
|
+
// no message_delta ever following — the stop must then still be
|
|
60
|
+
// preceded by an honest usage event built from that data.
|
|
61
|
+
let usageSeen = false;
|
|
62
|
+
let usageYielded = false;
|
|
63
|
+
let stopReasonSeen = false;
|
|
64
|
+
let stopReason = "end_turn";
|
|
65
|
+
const toolBuffer = new Map();
|
|
66
|
+
try {
|
|
67
|
+
for await (const event of stream) {
|
|
68
|
+
switch (event.type) {
|
|
69
|
+
case "message_start":
|
|
70
|
+
inputTokens = event.message.usage.input_tokens ?? null;
|
|
71
|
+
// D5: cache counters are READ from the SDK — never
|
|
72
|
+
// faked as zero.
|
|
73
|
+
cacheRead = event.message.usage.cache_read_input_tokens ?? null;
|
|
74
|
+
cacheWrite = event.message.usage.cache_creation_input_tokens ?? null;
|
|
75
|
+
if (event.message.usage.input_tokens !== undefined)
|
|
76
|
+
usageSeen = true;
|
|
77
|
+
break;
|
|
78
|
+
case "content_block_start": {
|
|
79
|
+
const block = event.content_block;
|
|
80
|
+
if (block.type === "text") {
|
|
81
|
+
yield { seq: 0, type: "text_start" };
|
|
82
|
+
}
|
|
83
|
+
else if (block.type === "tool_use") {
|
|
84
|
+
toolBuffer.set(event.index, { id: block.id, name: block.name, json: "" });
|
|
85
|
+
yield {
|
|
86
|
+
seq: 0,
|
|
87
|
+
type: "tool_call_start",
|
|
88
|
+
callId: block.id,
|
|
89
|
+
name: block.name,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "content_block_delta": {
|
|
95
|
+
const delta = event.delta;
|
|
96
|
+
if (delta.type === "text_delta") {
|
|
97
|
+
yield { seq: 0, type: "text_delta", text: delta.text };
|
|
98
|
+
}
|
|
99
|
+
else if (delta.type === "input_json_delta") {
|
|
100
|
+
const buffered = toolBuffer.get(event.index);
|
|
101
|
+
if (buffered) {
|
|
102
|
+
buffered.json += delta.partial_json;
|
|
103
|
+
yield {
|
|
104
|
+
seq: 0,
|
|
105
|
+
type: "tool_call_input_delta",
|
|
106
|
+
callId: buffered.id,
|
|
107
|
+
inputJsonDelta: delta.partial_json,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (delta.type === "thinking_delta") {
|
|
112
|
+
yield { seq: 0, type: "thinking", text: delta.thinking };
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "content_block_stop": {
|
|
117
|
+
const buffered = toolBuffer.get(event.index);
|
|
118
|
+
if (buffered) {
|
|
119
|
+
toolBuffer.delete(event.index);
|
|
120
|
+
let input = null;
|
|
121
|
+
try {
|
|
122
|
+
input = JSON.parse(buffered.json || "{}");
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
input = null; // never a silent repair
|
|
126
|
+
}
|
|
127
|
+
yield {
|
|
128
|
+
seq: 0,
|
|
129
|
+
type: "tool_call_end",
|
|
130
|
+
callId: buffered.id,
|
|
131
|
+
name: buffered.name,
|
|
132
|
+
input,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Text blocks end implicitly: the next TextStart (or
|
|
136
|
+
// the terminal) closes the previous block (ADR-0003).
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "message_delta":
|
|
140
|
+
stopReasonSeen = true;
|
|
141
|
+
stopReason = mapStopReason((event.delta.stop_reason ?? "error"));
|
|
142
|
+
outputTokens = event.usage?.output_tokens ?? null;
|
|
143
|
+
if (event.usage?.output_tokens !== undefined)
|
|
144
|
+
usageSeen = true;
|
|
145
|
+
usageYielded = true;
|
|
146
|
+
yield {
|
|
147
|
+
seq: 0,
|
|
148
|
+
type: "usage",
|
|
149
|
+
inputTokens,
|
|
150
|
+
outputTokens,
|
|
151
|
+
cacheRead,
|
|
152
|
+
cacheWrite,
|
|
153
|
+
known: usageSeen,
|
|
154
|
+
};
|
|
155
|
+
break;
|
|
156
|
+
case "message_stop":
|
|
157
|
+
// 六/九: EVERY stop path emits a usage first. If no
|
|
158
|
+
// message_delta yielded one, but message_start DID
|
|
159
|
+
// report usage data, an honest KNOWN usage is built
|
|
160
|
+
// from that data now — only a truly silent provider
|
|
161
|
+
// gets known:false (nulls, never faked). The
|
|
162
|
+
// ADR-0003 invariant "usage precedes stop" holds
|
|
163
|
+
// structurally.
|
|
164
|
+
if (!usageYielded) {
|
|
165
|
+
yield {
|
|
166
|
+
seq: 0,
|
|
167
|
+
type: "usage",
|
|
168
|
+
inputTokens,
|
|
169
|
+
outputTokens,
|
|
170
|
+
cacheRead,
|
|
171
|
+
cacheWrite,
|
|
172
|
+
known: usageSeen,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// D3: a message_stop with NO delta means the
|
|
176
|
+
// provider never reported a stop reason — that is
|
|
177
|
+
// an error, never a default end_turn.
|
|
178
|
+
yield { seq: 0, type: "stop", reason: stopReasonSeen ? stopReason : "error" };
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
throw toAnthropicError(err);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ── Mapping helpers ────────────────────────────────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Exhaustive over the SDK's CLOSED StopReason union (Area 6): a new SDK
|
|
192
|
+
* enum is a compile error here, and none of them degrades into `end_turn`.
|
|
193
|
+
* `pause_turn` and `refusal` are explicit non-completions.
|
|
194
|
+
*/
|
|
195
|
+
function mapStopReason(reason) {
|
|
196
|
+
switch (reason) {
|
|
197
|
+
case "end_turn":
|
|
198
|
+
return "end_turn";
|
|
199
|
+
case "max_tokens":
|
|
200
|
+
return "max_tokens";
|
|
201
|
+
case "stop_sequence":
|
|
202
|
+
return "stop_sequence";
|
|
203
|
+
case "tool_use":
|
|
204
|
+
return "tool_use";
|
|
205
|
+
case "pause_turn":
|
|
206
|
+
return "pause_turn";
|
|
207
|
+
case "refusal":
|
|
208
|
+
return "refusal";
|
|
209
|
+
case "model_context_window_exceeded":
|
|
210
|
+
return "context_window";
|
|
211
|
+
default: {
|
|
212
|
+
// The `never` annotation fails the typecheck when the SDK grows
|
|
213
|
+
// a new member; at runtime an unexpected value is an honest
|
|
214
|
+
// `error`, never a completed.
|
|
215
|
+
const _exhaustive = reason;
|
|
216
|
+
void _exhaustive;
|
|
217
|
+
return "error";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function toAnthropicContent(content) {
|
|
222
|
+
if (typeof content === "string") {
|
|
223
|
+
return [{ type: "text", text: content }];
|
|
224
|
+
}
|
|
225
|
+
return content.map((block) => {
|
|
226
|
+
if (block.type === "text")
|
|
227
|
+
return { type: "text", text: block.text };
|
|
228
|
+
return {
|
|
229
|
+
type: "image",
|
|
230
|
+
source: block.sourceType === "url"
|
|
231
|
+
? { type: "url", url: block.url ?? "" }
|
|
232
|
+
: { type: "base64", media_type: block.mediaType ?? "image/png", data: block.data ?? "" },
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function toAnthropicMessages(messages) {
|
|
237
|
+
const out = [];
|
|
238
|
+
for (const msg of messages) {
|
|
239
|
+
if (msg.role === "user") {
|
|
240
|
+
out.push({ role: "user", content: toAnthropicContent(msg.content) });
|
|
241
|
+
}
|
|
242
|
+
else if (msg.role === "assistant") {
|
|
243
|
+
out.push({
|
|
244
|
+
role: "assistant",
|
|
245
|
+
content: msg.blocks.map(toAnthropicBlock),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// tool result: Anthropic nests them as a user message.
|
|
250
|
+
out.push({
|
|
251
|
+
role: "user",
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "tool_result",
|
|
255
|
+
tool_use_id: msg.callId,
|
|
256
|
+
content: toAnthropicContent(msg.content),
|
|
257
|
+
is_error: msg.isError,
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
function toAnthropicBlock(block) {
|
|
266
|
+
return block.type === "text"
|
|
267
|
+
? { type: "text", text: block.text }
|
|
268
|
+
: { type: "tool_use", id: block.callId, name: block.name, input: block.input };
|
|
269
|
+
}
|
|
270
|
+
function toAnthropicTools(tools) {
|
|
271
|
+
return tools.map((t) => ({
|
|
272
|
+
name: t.name,
|
|
273
|
+
description: t.description,
|
|
274
|
+
input_schema: t.inputSchema,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
function toAnthropicError(err) {
|
|
278
|
+
// D4: connection-level failures are recognized, not lumped into unknown.
|
|
279
|
+
if (err instanceof Anthropic.APIConnectionTimeoutError) {
|
|
280
|
+
return { code: "timeout", retryable: true, message: err.message };
|
|
281
|
+
}
|
|
282
|
+
if (err instanceof Anthropic.APIConnectionError) {
|
|
283
|
+
return { code: "network", retryable: true, message: err.message };
|
|
284
|
+
}
|
|
285
|
+
if (err instanceof Anthropic.APIError) {
|
|
286
|
+
return mapApiError(err.status, err.message);
|
|
287
|
+
}
|
|
288
|
+
return err;
|
|
289
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vincemakes/kiso-provider-anthropic",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "kiso Anthropic adapter \u2014 official SDK wire events mapped to the kiso event union.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
21
|
+
"test": "vitest run"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@anthropic-ai/sdk": "^0.115.0",
|
|
25
|
+
"@vincemakes/kiso-core": "0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^26.1.2",
|
|
29
|
+
"typescript": "^5.7.2",
|
|
30
|
+
"vitest": "^3.0.0"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/vincemakes/kiso.git",
|
|
35
|
+
"directory": "packages/provider-anthropic"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/vincemakes/kiso/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/vincemakes/kiso/tree/main/packages/provider-anthropic#readme"
|
|
41
|
+
}
|