@byokit/accounts 0.10.0 → 0.12.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/CHANGELOG.md +16 -0
- package/NOTICE +30 -0
- package/README.md +132 -4
- package/dist/accounts.d.ts +46 -5
- package/dist/accounts.js +63 -6
- package/dist/anthropic.d.ts +236 -0
- package/dist/anthropic.js +268 -0
- package/dist/catalogue.d.ts +3 -0
- package/dist/catalogue.js +3 -4
- package/dist/catalogue.json +32 -0
- package/dist/claude-plan.d.ts +51 -0
- package/dist/claude-plan.js +167 -0
- package/dist/node-stores.d.ts +2 -0
- package/dist/node-stores.js +13 -1
- package/dist/portable.d.ts +4 -2
- package/dist/portable.js +3 -1
- package/dist/responses.d.ts +2 -0
- package/dist/responses.js +1 -0
- package/dist/stores.d.ts +5 -0
- package/dist/stores.js +4 -0
- package/package.json +3 -2
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { IncompleteError, type ResponseResult } from './responses.ts';
|
|
2
|
+
export type AnthropicCacheControl = {
|
|
3
|
+
type: 'ephemeral';
|
|
4
|
+
ttl?: '5m' | '1h';
|
|
5
|
+
};
|
|
6
|
+
export type AnthropicText = {
|
|
7
|
+
type: 'text';
|
|
8
|
+
text: string;
|
|
9
|
+
cache_control?: AnthropicCacheControl;
|
|
10
|
+
citations?: unknown[];
|
|
11
|
+
};
|
|
12
|
+
export type AnthropicImage = {
|
|
13
|
+
type: 'image';
|
|
14
|
+
source: {
|
|
15
|
+
type: 'base64';
|
|
16
|
+
media_type: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
|
|
17
|
+
data: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: 'url';
|
|
20
|
+
url: string;
|
|
21
|
+
};
|
|
22
|
+
cache_control?: AnthropicCacheControl;
|
|
23
|
+
};
|
|
24
|
+
export type AnthropicToolUse = {
|
|
25
|
+
type: 'tool_use';
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
input: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
export type AnthropicThinking = {
|
|
31
|
+
type: 'thinking';
|
|
32
|
+
thinking: string;
|
|
33
|
+
signature: string;
|
|
34
|
+
};
|
|
35
|
+
export type AnthropicContent = AnthropicText | AnthropicImage | AnthropicToolUse | AnthropicThinking | {
|
|
36
|
+
type: 'redacted_thinking';
|
|
37
|
+
data: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'tool_result';
|
|
40
|
+
tool_use_id: string;
|
|
41
|
+
content?: string | AnthropicContent[];
|
|
42
|
+
is_error?: boolean;
|
|
43
|
+
cache_control?: AnthropicCacheControl;
|
|
44
|
+
} | {
|
|
45
|
+
type: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
};
|
|
48
|
+
export type AnthropicMessage = {
|
|
49
|
+
role: 'user' | 'assistant';
|
|
50
|
+
content: string | AnthropicContent[];
|
|
51
|
+
};
|
|
52
|
+
export type AnthropicTool = {
|
|
53
|
+
name: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
input_schema: {
|
|
56
|
+
type: 'object';
|
|
57
|
+
properties?: Record<string, unknown>;
|
|
58
|
+
required?: string[];
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
cache_control?: AnthropicCacheControl;
|
|
62
|
+
strict?: boolean;
|
|
63
|
+
defer_loading?: boolean;
|
|
64
|
+
input_examples?: Record<string, unknown>[];
|
|
65
|
+
allowed_callers?: string[];
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
} | {
|
|
68
|
+
type: string;
|
|
69
|
+
name: string;
|
|
70
|
+
[key: string]: unknown;
|
|
71
|
+
};
|
|
72
|
+
export type AnthropicToolChoice = {
|
|
73
|
+
type: 'auto' | 'any' | 'none';
|
|
74
|
+
disable_parallel_tool_use?: boolean;
|
|
75
|
+
} | {
|
|
76
|
+
type: 'tool';
|
|
77
|
+
name: string;
|
|
78
|
+
disable_parallel_tool_use?: boolean;
|
|
79
|
+
};
|
|
80
|
+
export type AnthropicUsage = {
|
|
81
|
+
input_tokens: number;
|
|
82
|
+
output_tokens: number;
|
|
83
|
+
cache_creation_input_tokens?: number;
|
|
84
|
+
cache_read_input_tokens?: number;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
};
|
|
87
|
+
export type AnthropicResponse = {
|
|
88
|
+
id: string;
|
|
89
|
+
type: 'message';
|
|
90
|
+
role: 'assistant';
|
|
91
|
+
model: string;
|
|
92
|
+
content: AnthropicContent[];
|
|
93
|
+
stop_reason: string | null;
|
|
94
|
+
stop_sequence: string | null;
|
|
95
|
+
usage: AnthropicUsage;
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
};
|
|
98
|
+
/** Native Messages options; no translation or silent selection of a billed provider/model. */
|
|
99
|
+
export type AnthropicRequest = {
|
|
100
|
+
model: string;
|
|
101
|
+
max_tokens: number;
|
|
102
|
+
messages: AnthropicMessage[];
|
|
103
|
+
system?: string | AnthropicText[];
|
|
104
|
+
tools?: AnthropicTool[];
|
|
105
|
+
tool_choice?: AnthropicToolChoice;
|
|
106
|
+
thinking?: {
|
|
107
|
+
type: 'enabled';
|
|
108
|
+
budget_tokens: number;
|
|
109
|
+
display?: 'summarized' | 'omitted' | 'updates';
|
|
110
|
+
} | {
|
|
111
|
+
type: 'disabled';
|
|
112
|
+
} | {
|
|
113
|
+
type: 'adaptive';
|
|
114
|
+
display?: 'summarized' | 'omitted' | 'updates';
|
|
115
|
+
};
|
|
116
|
+
stop_sequences?: string[];
|
|
117
|
+
metadata?: {
|
|
118
|
+
user_id?: string;
|
|
119
|
+
};
|
|
120
|
+
temperature?: number;
|
|
121
|
+
top_p?: number;
|
|
122
|
+
top_k?: number;
|
|
123
|
+
service_tier?: 'auto' | 'standard_only';
|
|
124
|
+
output_config?: {
|
|
125
|
+
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
126
|
+
format?: {
|
|
127
|
+
type: 'json_schema';
|
|
128
|
+
schema: Record<string, unknown>;
|
|
129
|
+
};
|
|
130
|
+
[key: string]: unknown;
|
|
131
|
+
};
|
|
132
|
+
cache_control?: AnthropicCacheControl;
|
|
133
|
+
/** Additional native options are passed through unchanged. */
|
|
134
|
+
[key: string]: unknown;
|
|
135
|
+
};
|
|
136
|
+
export type AnthropicResult = ResponseResult & {
|
|
137
|
+
status: 'completed' | 'incomplete';
|
|
138
|
+
incompleteReason?: string;
|
|
139
|
+
usage: AnthropicUsage;
|
|
140
|
+
raw: AnthropicResponse;
|
|
141
|
+
};
|
|
142
|
+
/** The shared incomplete contract, with native usage/raw retained on the partial result. */
|
|
143
|
+
export declare class AnthropicIncompleteError extends IncompleteError {
|
|
144
|
+
result: AnthropicResult;
|
|
145
|
+
constructor(reason: string, result: AnthropicResult);
|
|
146
|
+
}
|
|
147
|
+
export type AnthropicStreamEvent = {
|
|
148
|
+
type: 'text_delta';
|
|
149
|
+
delta: string;
|
|
150
|
+
} | {
|
|
151
|
+
type: 'tool_use_delta';
|
|
152
|
+
index: number;
|
|
153
|
+
id: string;
|
|
154
|
+
name: string;
|
|
155
|
+
delta: string;
|
|
156
|
+
} | {
|
|
157
|
+
type: 'tool_use';
|
|
158
|
+
index: number;
|
|
159
|
+
tool: AnthropicToolUse;
|
|
160
|
+
} | {
|
|
161
|
+
type: 'content_block';
|
|
162
|
+
index: number;
|
|
163
|
+
block: AnthropicContent;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'message_start';
|
|
166
|
+
message: AnthropicResponse;
|
|
167
|
+
} | {
|
|
168
|
+
type: 'message_delta';
|
|
169
|
+
delta: Partial<AnthropicResponse>;
|
|
170
|
+
usage?: Partial<AnthropicUsage>;
|
|
171
|
+
} | {
|
|
172
|
+
type: 'message_stop';
|
|
173
|
+
message: AnthropicResponse;
|
|
174
|
+
} | {
|
|
175
|
+
type: 'incomplete';
|
|
176
|
+
reason: string;
|
|
177
|
+
response: AnthropicResponse;
|
|
178
|
+
};
|
|
179
|
+
export type AnthropicAsk = AnthropicRequest & {
|
|
180
|
+
onText?: (delta: string) => void;
|
|
181
|
+
onEvent?: (event: AnthropicStreamEvent) => void;
|
|
182
|
+
signal?: AbortSignal;
|
|
183
|
+
/** Return metadata even without tools. */
|
|
184
|
+
result?: boolean;
|
|
185
|
+
};
|
|
186
|
+
export type AnthropicOptions = {
|
|
187
|
+
/** API key (billed per use), passed by the app. Never loaded from env/files. */
|
|
188
|
+
key: string;
|
|
189
|
+
fetch?: typeof fetch;
|
|
190
|
+
/** An app-owned proxy or a stand-in in tests. */
|
|
191
|
+
base?: string;
|
|
192
|
+
/** Native beta headers, only when the app explicitly requests them. */
|
|
193
|
+
betas?: readonly string[];
|
|
194
|
+
};
|
|
195
|
+
/** Recorded Messages SSE, split at arbitrary UTF-8/chunk boundaries. Completion requires message_stop. */
|
|
196
|
+
export declare function anthropicSseReader(onText?: (delta: string) => void, onEvent?: (event: AnthropicStreamEvent) => void): {
|
|
197
|
+
push(chunk: string): void;
|
|
198
|
+
result: () => AnthropicResult;
|
|
199
|
+
end(): string;
|
|
200
|
+
};
|
|
201
|
+
/** Explicit API-key backend. Use respond({ result: true }) for usage/raw. Incomplete answers always throw. */
|
|
202
|
+
export declare function anthropic(opts: AnthropicOptions): {
|
|
203
|
+
respond: {
|
|
204
|
+
(o: AnthropicAsk & {
|
|
205
|
+
result: true;
|
|
206
|
+
}): Promise<AnthropicResult>;
|
|
207
|
+
(o: AnthropicAsk & {
|
|
208
|
+
tools: AnthropicTool[];
|
|
209
|
+
}): Promise<AnthropicResult>;
|
|
210
|
+
(o: AnthropicAsk & {
|
|
211
|
+
tools?: undefined;
|
|
212
|
+
result?: false;
|
|
213
|
+
}): Promise<string>;
|
|
214
|
+
(o: AnthropicAsk): Promise<string | AnthropicResult>;
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
/** Internal auth seam for the API-key and subscription routes. */
|
|
218
|
+
export declare function anthropicMessages(opts: Pick<AnthropicOptions, 'fetch' | 'base' | 'betas'> & {
|
|
219
|
+
headers: Record<string, string>;
|
|
220
|
+
prepare?: (request: AnthropicRequest) => AnthropicRequest;
|
|
221
|
+
safeErrors?: boolean;
|
|
222
|
+
}): {
|
|
223
|
+
respond: {
|
|
224
|
+
(o: AnthropicAsk & {
|
|
225
|
+
result: true;
|
|
226
|
+
}): Promise<AnthropicResult>;
|
|
227
|
+
(o: AnthropicAsk & {
|
|
228
|
+
tools: AnthropicTool[];
|
|
229
|
+
}): Promise<AnthropicResult>;
|
|
230
|
+
(o: AnthropicAsk & {
|
|
231
|
+
tools?: undefined;
|
|
232
|
+
result?: false;
|
|
233
|
+
}): Promise<string>;
|
|
234
|
+
(o: AnthropicAsk): Promise<string | AnthropicResult>;
|
|
235
|
+
};
|
|
236
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Messages over fetch alone. Authentication is explicit and separate from the request, so a future auth route can
|
|
2
|
+
// supply different headers without changing messages, tools, events or results. No environment or credential reads.
|
|
3
|
+
import { IncompleteError, ResponseError } from "./responses.js";
|
|
4
|
+
/** The shared incomplete contract, with native usage/raw retained on the partial result. */
|
|
5
|
+
export class AnthropicIncompleteError extends IncompleteError {
|
|
6
|
+
constructor(reason, result) {
|
|
7
|
+
super(reason, result);
|
|
8
|
+
this.message = 'Anthropic cut off its answer before it was complete.';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const record = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
12
|
+
const error = (status, value) => {
|
|
13
|
+
const e = record(value) && record(value.error) ? value.error : {};
|
|
14
|
+
const kind = status === 429 || e.type === 'rate_limit_error' ? 'rate_limit'
|
|
15
|
+
: status === 401 || status === 403 || e.type === 'authentication_error' ? 'signed_out'
|
|
16
|
+
: status >= 500 || e.type === 'overloaded_error' ? 'overloaded' : null;
|
|
17
|
+
return new ResponseError(typeof e.message === 'string' ? e.message : 'Anthropic could not answer. Try again.', kind);
|
|
18
|
+
};
|
|
19
|
+
/** Recorded Messages SSE, split at arbitrary UTF-8/chunk boundaries. Completion requires message_stop. */
|
|
20
|
+
export function anthropicSseReader(onText, onEvent) {
|
|
21
|
+
let buffer = '', message, stopped = false, finished;
|
|
22
|
+
const blocks = new Map();
|
|
23
|
+
const fail = () => new ResponseError('Anthropic stopped before completing its answer.', 'network');
|
|
24
|
+
const notifyText = (delta) => { if (delta) {
|
|
25
|
+
onText?.(delta);
|
|
26
|
+
onEvent?.({ type: 'text_delta', delta });
|
|
27
|
+
} };
|
|
28
|
+
const event = (block) => {
|
|
29
|
+
const data = block.split('\n').filter((l) => l.startsWith('data:')).map((l) => l.slice(5).replace(/^ /, '')).join('\n');
|
|
30
|
+
if (!data)
|
|
31
|
+
return;
|
|
32
|
+
let e;
|
|
33
|
+
try {
|
|
34
|
+
e = JSON.parse(data);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new ResponseError("Anthropic's answer could not be read.", null);
|
|
38
|
+
}
|
|
39
|
+
if (!record(e))
|
|
40
|
+
throw fail();
|
|
41
|
+
if (e.type === 'error')
|
|
42
|
+
throw error(0, e);
|
|
43
|
+
if (stopped)
|
|
44
|
+
return;
|
|
45
|
+
if (e.type === 'message_start') {
|
|
46
|
+
if (message || !record(e.message) || !Array.isArray(e.message.content) || !record(e.message.usage))
|
|
47
|
+
throw fail();
|
|
48
|
+
message = { ...e.message, content: [...e.message.content], usage: { ...e.message.usage } };
|
|
49
|
+
onEvent?.({ type: 'message_start', message: structuredCopy(message) });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (e.type === 'ping')
|
|
53
|
+
return;
|
|
54
|
+
if (e.type === 'content_block_start') {
|
|
55
|
+
if (!message || !Number.isSafeInteger(e.index) || e.index < 0 || blocks.has(e.index) || !record(e.content_block))
|
|
56
|
+
throw fail();
|
|
57
|
+
const b = { ...e.content_block };
|
|
58
|
+
blocks.set(e.index, { block: b, json: '', stopped: false, landed: false });
|
|
59
|
+
message.content[e.index] = b;
|
|
60
|
+
if (b.type === 'text' && typeof b.text === 'string')
|
|
61
|
+
notifyText(b.text);
|
|
62
|
+
}
|
|
63
|
+
else if (e.type === 'content_block_delta') {
|
|
64
|
+
const at = blocks.get(e.index);
|
|
65
|
+
if (!at || at.stopped || !record(e.delta))
|
|
66
|
+
throw fail();
|
|
67
|
+
const d = e.delta;
|
|
68
|
+
if (d.type === 'text_delta' && typeof d.text === 'string') {
|
|
69
|
+
if (at.block.type !== 'text')
|
|
70
|
+
throw fail();
|
|
71
|
+
at.block.text += d.text;
|
|
72
|
+
notifyText(d.text);
|
|
73
|
+
}
|
|
74
|
+
else if (d.type === 'input_json_delta' && typeof d.partial_json === 'string') {
|
|
75
|
+
if (at.block.type !== 'tool_use' && at.block.type !== 'server_tool_use')
|
|
76
|
+
throw fail();
|
|
77
|
+
at.json += d.partial_json;
|
|
78
|
+
onEvent?.({ type: 'tool_use_delta', index: e.index, id: at.block.id, name: at.block.name, delta: d.partial_json });
|
|
79
|
+
}
|
|
80
|
+
else if (d.type === 'thinking_delta' && typeof d.thinking === 'string')
|
|
81
|
+
at.block.thinking = (at.block.thinking ?? '') + d.thinking;
|
|
82
|
+
else if (d.type === 'signature_delta' && typeof d.signature === 'string')
|
|
83
|
+
at.block.signature = (at.block.signature ?? '') + d.signature;
|
|
84
|
+
else if (d.type === 'citations_delta' && record(d.citation))
|
|
85
|
+
at.block.citations = [...(at.block.citations ?? []), d.citation];
|
|
86
|
+
}
|
|
87
|
+
else if (e.type === 'content_block_stop') {
|
|
88
|
+
const at = blocks.get(e.index);
|
|
89
|
+
if (!at || at.stopped)
|
|
90
|
+
throw fail();
|
|
91
|
+
at.stopped = true;
|
|
92
|
+
// Emit valid blocks as they land. An interrupted JSON block must wait for the stop reason: max_tokens
|
|
93
|
+
// preserves that partial call without emitting an executable tool-use event.
|
|
94
|
+
if (at.json) {
|
|
95
|
+
try {
|
|
96
|
+
const input = JSON.parse(at.json);
|
|
97
|
+
if (record(input))
|
|
98
|
+
at.block.input = input;
|
|
99
|
+
else
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
onEvent?.({ type: 'content_block', index: e.index, block: structuredCopy(at.block) });
|
|
107
|
+
if (at.block.type === 'tool_use') {
|
|
108
|
+
if (typeof at.block.id !== 'string' || typeof at.block.name !== 'string' || !record(at.block.input))
|
|
109
|
+
throw fail();
|
|
110
|
+
onEvent?.({ type: 'tool_use', index: e.index, tool: structuredCopy(at.block) });
|
|
111
|
+
}
|
|
112
|
+
at.landed = true;
|
|
113
|
+
}
|
|
114
|
+
else if (e.type === 'message_delta') {
|
|
115
|
+
if (!message || !record(e.delta))
|
|
116
|
+
throw fail();
|
|
117
|
+
Object.assign(message, e.delta);
|
|
118
|
+
if (record(e.usage))
|
|
119
|
+
Object.assign(message.usage, e.usage); // cumulative, never summed
|
|
120
|
+
onEvent?.({ type: 'message_delta', delta: e.delta, ...(record(e.usage) ? { usage: e.usage } : {}) });
|
|
121
|
+
}
|
|
122
|
+
else if (e.type === 'message_stop') {
|
|
123
|
+
if (!message || !message.stop_reason || [...blocks.values()].some((b) => !b.stopped))
|
|
124
|
+
throw fail();
|
|
125
|
+
stopped = true;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const drain = (final) => {
|
|
129
|
+
let tail = '';
|
|
130
|
+
if (!final && buffer.endsWith('\r')) {
|
|
131
|
+
tail = '\r';
|
|
132
|
+
buffer = buffer.slice(0, -1);
|
|
133
|
+
}
|
|
134
|
+
const parts = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n\n');
|
|
135
|
+
buffer = (final ? '' : parts.pop()) + tail;
|
|
136
|
+
for (const part of parts)
|
|
137
|
+
event(part);
|
|
138
|
+
};
|
|
139
|
+
const result = () => {
|
|
140
|
+
if (finished) {
|
|
141
|
+
if (finished.status === 'incomplete')
|
|
142
|
+
throw new AnthropicIncompleteError(finished.incompleteReason, finished);
|
|
143
|
+
return finished;
|
|
144
|
+
}
|
|
145
|
+
drain(true);
|
|
146
|
+
if (!stopped || !message)
|
|
147
|
+
throw fail();
|
|
148
|
+
const incomplete = message.stop_reason === 'max_tokens' || message.stop_reason === 'refusal';
|
|
149
|
+
const output = [];
|
|
150
|
+
for (const [index, at] of [...blocks].sort(([a], [b]) => a - b)) {
|
|
151
|
+
if (at.json) {
|
|
152
|
+
try {
|
|
153
|
+
const input = JSON.parse(at.json);
|
|
154
|
+
if (!record(input))
|
|
155
|
+
throw new Error('not an object');
|
|
156
|
+
at.block.input = input;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
if (!incomplete)
|
|
160
|
+
throw new ResponseError("Anthropic's tool call could not be read.", null);
|
|
161
|
+
at.block.partial_json = at.json;
|
|
162
|
+
delete at.block.input;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const b = at.block;
|
|
166
|
+
if (!at.landed)
|
|
167
|
+
onEvent?.({ type: 'content_block', index, block: b });
|
|
168
|
+
if (b.type === 'tool_use' && record(b.input)) {
|
|
169
|
+
output.push({ type: 'function_call', call_id: b.id, name: b.name, arguments: JSON.stringify(b.input) });
|
|
170
|
+
if (!incomplete && !at.landed)
|
|
171
|
+
onEvent?.({ type: 'tool_use', index, tool: b });
|
|
172
|
+
}
|
|
173
|
+
else if (b.type === 'thinking' || b.type === 'redacted_thinking')
|
|
174
|
+
output.push({ ...b, type: 'reasoning' });
|
|
175
|
+
}
|
|
176
|
+
const text = message.content.filter((b) => b.type === 'text').map((b) => 'text' in b && typeof b.text === 'string' ? b.text : '').join('');
|
|
177
|
+
if (text)
|
|
178
|
+
output.unshift({ type: 'message', id: message.id, role: message.role, content: [{ type: 'output_text', text }] });
|
|
179
|
+
finished = { text, output, status: incomplete ? 'incomplete' : 'completed', ...(incomplete ? { incompleteReason: message.stop_reason } : {}), usage: message.usage, raw: message };
|
|
180
|
+
if (incomplete)
|
|
181
|
+
onEvent?.({ type: 'incomplete', reason: message.stop_reason, response: message });
|
|
182
|
+
onEvent?.({ type: 'message_stop', message });
|
|
183
|
+
if (incomplete)
|
|
184
|
+
throw new AnthropicIncompleteError(message.stop_reason, finished);
|
|
185
|
+
return finished;
|
|
186
|
+
};
|
|
187
|
+
return { push(chunk) { buffer += chunk; drain(false); }, result, end() { return result().text; } };
|
|
188
|
+
}
|
|
189
|
+
const structuredCopy = (value) => JSON.parse(JSON.stringify(value));
|
|
190
|
+
/** Explicit API-key backend. Use respond({ result: true }) for usage/raw. Incomplete answers always throw. */
|
|
191
|
+
export function anthropic(opts) {
|
|
192
|
+
if (!opts.key.trim())
|
|
193
|
+
throw new Error('Anthropic needs an API key (billed per use).');
|
|
194
|
+
return anthropicMessages({ ...opts, headers: { 'x-api-key': opts.key } });
|
|
195
|
+
}
|
|
196
|
+
/** Internal auth seam for the API-key and subscription routes. */
|
|
197
|
+
export function anthropicMessages(opts) {
|
|
198
|
+
async function respond(o) {
|
|
199
|
+
try {
|
|
200
|
+
return await run(o);
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (!opts.safeErrors || e instanceof AnthropicIncompleteError)
|
|
204
|
+
throw e;
|
|
205
|
+
throw new ResponseError('Claude could not answer. Try again or sign in again.', e instanceof ResponseError ? e.kind : 'network');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function run(o) {
|
|
209
|
+
const { onText, onEvent, signal, result, ...request } = o;
|
|
210
|
+
if (!request.model || !Number.isSafeInteger(request.max_tokens) || request.max_tokens <= 0)
|
|
211
|
+
throw new Error('Anthropic needs a model and a positive max_tokens.');
|
|
212
|
+
const res = await (opts.fetch ?? fetch)(`${(opts.base ?? 'https://api.anthropic.com').replace(/\/$/, '')}/v1/messages`, {
|
|
213
|
+
method: 'POST', signal,
|
|
214
|
+
headers: { 'content-type': 'application/json', accept: 'text/event-stream', 'anthropic-version': '2023-06-01', ...opts.headers,
|
|
215
|
+
...(opts.betas?.length ? { 'anthropic-beta': opts.betas.join(',') } : {}) },
|
|
216
|
+
body: JSON.stringify({ ...(opts.prepare ? opts.prepare(request) : request), stream: true }),
|
|
217
|
+
});
|
|
218
|
+
if (!res.ok) {
|
|
219
|
+
let body;
|
|
220
|
+
try {
|
|
221
|
+
body = await res.json();
|
|
222
|
+
}
|
|
223
|
+
catch { }
|
|
224
|
+
throw error(res.status, body);
|
|
225
|
+
}
|
|
226
|
+
const reader = anthropicSseReader(onText, onEvent);
|
|
227
|
+
const body = res.body;
|
|
228
|
+
if (res.headers?.get('content-type')?.includes('application/json')) {
|
|
229
|
+
let message;
|
|
230
|
+
try {
|
|
231
|
+
message = await res.json();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
throw new ResponseError("Anthropic's answer could not be read.", null);
|
|
235
|
+
}
|
|
236
|
+
if (!record(message) || !Array.isArray(message.content))
|
|
237
|
+
throw new ResponseError("Anthropic's answer could not be read.", null);
|
|
238
|
+
const emit = (event) => reader.push(`data: ${JSON.stringify(event)}\n\n`);
|
|
239
|
+
emit({ type: 'message_start', message: { ...message, content: [] } });
|
|
240
|
+
for (const [index, block] of message.content.entries()) {
|
|
241
|
+
emit({ type: 'content_block_start', index, content_block: block });
|
|
242
|
+
emit({ type: 'content_block_stop', index });
|
|
243
|
+
}
|
|
244
|
+
emit({ type: 'message_delta', delta: { stop_reason: message.stop_reason, stop_sequence: message.stop_sequence }, usage: message.usage });
|
|
245
|
+
emit({ type: 'message_stop' });
|
|
246
|
+
}
|
|
247
|
+
else if (body?.getReader && typeof TextDecoder !== 'undefined') {
|
|
248
|
+
const r = body.getReader(), decoder = new TextDecoder();
|
|
249
|
+
try {
|
|
250
|
+
for (let c = await r.read(); !c.done; c = await r.read())
|
|
251
|
+
reader.push(decoder.decode(c.value, { stream: true }));
|
|
252
|
+
reader.push(decoder.decode());
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
await r.cancel().catch(() => { });
|
|
256
|
+
throw e;
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
r.releaseLock();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else
|
|
263
|
+
reader.push(await res.text());
|
|
264
|
+
const answer = reader.result();
|
|
265
|
+
return o.tools || result ? answer : answer.text;
|
|
266
|
+
}
|
|
267
|
+
return { respond };
|
|
268
|
+
}
|
package/dist/catalogue.d.ts
CHANGED
package/dist/catalogue.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// The AI accounts a person can bring, by the name they know, with each provider's billing and terms status as data
|
|
2
2
|
// (catalogue.json, readable from Kotlin too). The kit labels; the app decides what to offer. API-billed rows are never
|
|
3
|
-
// offered by default; an app names them explicitly.
|
|
4
|
-
//
|
|
5
|
-
// Kimi refuses anything but coding agents).
|
|
3
|
+
// offered by default; an app names them explicitly. Anthropic here is the app-passed API-key route; authentication
|
|
4
|
+
// is separate from Messages, allowing additional opt-in auth routes to be implemented independently.
|
|
6
5
|
import CATALOGUE from './catalogue.json' with { type: 'json' };
|
|
7
6
|
export const PROVIDERS = Object.fromEntries(Object.entries(CATALOGUE).map(([key, p]) => [key, { key, ...p }]));
|
|
8
7
|
export function provider(key) {
|
|
@@ -13,4 +12,4 @@ export function provider(key) {
|
|
|
13
12
|
}
|
|
14
13
|
/** What an app offers: the keys it names, in its order, or every subscription provider not hidden by default.
|
|
15
14
|
* API-billed rows are never in the default: an app offers them only by naming them. */
|
|
16
|
-
export const offered = (keys) => keys ? keys.map(provider) : Object.values(PROVIDERS).filter((p) => !p.hidden && p.billing === 'subscription');
|
|
15
|
+
export const offered = (keys) => keys ? keys.map(provider) : Object.values(PROVIDERS).filter((p) => !p.hidden && p.offer !== false && p.billing === 'subscription');
|
package/dist/catalogue.json
CHANGED
|
@@ -54,5 +54,37 @@
|
|
|
54
54
|
"hidden": true,
|
|
55
55
|
"why": "GitHub allows Copilot sign-in only in apps it has partnered with; this uses VS Code's client.",
|
|
56
56
|
"source": "https://github.blog/changelog/2026-01-16-github-copilot-now-supports-opencode"
|
|
57
|
+
},
|
|
58
|
+
"anthropic": {
|
|
59
|
+
"pi": "anthropic",
|
|
60
|
+
"name": "Claude",
|
|
61
|
+
"company": "Anthropic",
|
|
62
|
+
"models": {
|
|
63
|
+
"strong": "claude-opus-5-5"
|
|
64
|
+
},
|
|
65
|
+
"billing": "api",
|
|
66
|
+
"terms": "allowed",
|
|
67
|
+
"hidden": false,
|
|
68
|
+
"auth": "api-key",
|
|
69
|
+
"label": "API key (billed per use)",
|
|
70
|
+
"offer": false,
|
|
71
|
+
"why": "The app passes an Anthropic API key explicitly. API key (billed per use), never a default or subscription fallback.",
|
|
72
|
+
"source": "https://platform.claude.com/docs/en/api/overview"
|
|
73
|
+
},
|
|
74
|
+
"claude": {
|
|
75
|
+
"pi": "byokit-claude-plan",
|
|
76
|
+
"name": "Claude",
|
|
77
|
+
"label": "Claude Pro/Max",
|
|
78
|
+
"company": "Anthropic",
|
|
79
|
+
"models": {
|
|
80
|
+
"strong": "claude-opus-5-5"
|
|
81
|
+
},
|
|
82
|
+
"clientId": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
83
|
+
"billing": "subscription",
|
|
84
|
+
"auth": "oauth",
|
|
85
|
+
"terms": "grey",
|
|
86
|
+
"hidden": false,
|
|
87
|
+
"why": "Anthropic prohibits third-party Claude.ai sign-in without approval; this route may stop working or lead to account restrictions.",
|
|
88
|
+
"source": "https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use"
|
|
57
89
|
}
|
|
58
90
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CredentialStore } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { AuthHost } from './accounts.ts';
|
|
3
|
+
export declare const CLAUDE_PLAN_ID = "byokit-claude-plan";
|
|
4
|
+
export declare const CLAUDE_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
5
|
+
/** A refused/uncertain rotation requires a new sign-in; its old refresh token must not be tried again. */
|
|
6
|
+
export declare class ClaudePlanExpiredError extends Error {
|
|
7
|
+
readonly code = "CLAUDE_PLAN_EXPIRED";
|
|
8
|
+
readonly status = 401;
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
export declare class ClaudePlanPlatformError extends Error {
|
|
12
|
+
readonly code = "CLAUDE_PLAN_UNSUPPORTED_PLATFORM";
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export type ClaudePlanOptions = {
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
/** Stand-ins for offline tests. Production uses the Hermes-reference endpoints directly. */
|
|
18
|
+
authorizeUrl?: string;
|
|
19
|
+
tokenUrl?: string;
|
|
20
|
+
/** On React Native, pass a Web Crypto implementation supplied by the app. */
|
|
21
|
+
crypto?: Pick<Crypto, 'getRandomValues' | 'subtle'>;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
};
|
|
24
|
+
/** Pending state/verifier never enter persistent storage. */
|
|
25
|
+
export declare function claudeAuthorization(options?: ClaudePlanOptions): Promise<{
|
|
26
|
+
url: string;
|
|
27
|
+
verifier: string;
|
|
28
|
+
state: string;
|
|
29
|
+
redirect: string;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function claudeCode(paste: string, state: string): string;
|
|
32
|
+
/** Adds only BYOKit's own Claude route; every other provider still belongs to the supplied engine. */
|
|
33
|
+
export declare function withClaudePlan(engine: AuthHost, credentials: CredentialStore, lockKey: CredentialStore, options?: ClaudePlanOptions): AuthHost;
|
|
34
|
+
/** Hermes's baseline native-client fingerprint. No installed CLI is consulted for its version. */
|
|
35
|
+
export declare function claudePlanMessages(access: string, options?: {
|
|
36
|
+
fetch?: typeof fetch;
|
|
37
|
+
}): {
|
|
38
|
+
respond: {
|
|
39
|
+
(o: import("./anthropic.ts").AnthropicAsk & {
|
|
40
|
+
result: true;
|
|
41
|
+
}): Promise<import("./anthropic.ts").AnthropicResult>;
|
|
42
|
+
(o: import("./anthropic.ts").AnthropicAsk & {
|
|
43
|
+
tools: import("./anthropic.ts").AnthropicTool[];
|
|
44
|
+
}): Promise<import("./anthropic.ts").AnthropicResult>;
|
|
45
|
+
(o: import("./anthropic.ts").AnthropicAsk & {
|
|
46
|
+
tools?: undefined;
|
|
47
|
+
result?: false;
|
|
48
|
+
}): Promise<string>;
|
|
49
|
+
(o: import("./anthropic.ts").AnthropicAsk): Promise<string | import("./anthropic.ts").AnthropicResult>;
|
|
50
|
+
};
|
|
51
|
+
};
|