@byokit/accounts 0.11.0 → 0.13.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 +15 -0
- package/NOTICE +30 -0
- package/README.md +187 -4
- package/dist/accounts.d.ts +37 -1
- package/dist/accounts.js +49 -5
- package/dist/anthropic.d.ts +236 -0
- package/dist/anthropic.js +268 -0
- package/dist/catalogue.d.ts +10 -0
- package/dist/catalogue.js +3 -4
- package/dist/catalogue.json +66 -4
- package/dist/claude-plan.d.ts +51 -0
- package/dist/claude-plan.js +167 -0
- package/dist/multi.d.ts +77 -0
- package/dist/multi.js +134 -0
- package/dist/node-stores.d.ts +2 -0
- package/dist/node-stores.js +13 -1
- package/dist/portable.d.ts +6 -3
- package/dist/portable.js +4 -1
- package/dist/stores.d.ts +5 -0
- package/dist/stores.js +4 -0
- package/dist/words.json +35 -3
- 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
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export type Terms = 'allowed' | 'grey' | 'partner' | 'forbidden';
|
|
2
2
|
/** How the person pays: their plan (`subscription`), or per-use charges to their account (`api`, never offered by default). */
|
|
3
3
|
export type Billing = 'subscription' | 'api';
|
|
4
|
+
/** Terms assessment for choosing between several accounts; descriptive, never an eligibility gate. */
|
|
5
|
+
export type MultiAccountTerms = {
|
|
6
|
+
terms: Terms;
|
|
7
|
+
why: string;
|
|
8
|
+
source: string;
|
|
9
|
+
};
|
|
4
10
|
/** `callbackPort`: where the provider sends the browser back after its own sign-in page, fixed for the client Pi signs in as.
|
|
5
11
|
* `revoke`: where signing out ends the sign-in on the provider's side too, for the client `clientId`. */
|
|
6
12
|
export type Provider = {
|
|
@@ -16,10 +22,14 @@ export type Provider = {
|
|
|
16
22
|
clientId?: string;
|
|
17
23
|
revoke?: string;
|
|
18
24
|
billing: Billing;
|
|
25
|
+
auth?: 'api-key' | 'oauth';
|
|
26
|
+
label?: string;
|
|
27
|
+
offer?: boolean;
|
|
19
28
|
terms: Terms;
|
|
20
29
|
hidden: boolean;
|
|
21
30
|
why: string;
|
|
22
31
|
source: string;
|
|
32
|
+
multiAccount: MultiAccountTerms;
|
|
23
33
|
};
|
|
24
34
|
export declare const PROVIDERS: Record<string, Provider>;
|
|
25
35
|
export declare function provider(key: string): Provider;
|
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
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
"terms": "grey",
|
|
15
15
|
"hidden": false,
|
|
16
16
|
"why": "Signs in through Codex's own sign-in. OpenAI documents it for Codex, not for other apps, and has endorsed one other app using it.",
|
|
17
|
-
"source": "https://developers.openai.com/codex/auth"
|
|
17
|
+
"source": "https://developers.openai.com/codex/auth",
|
|
18
|
+
"multiAccount": {
|
|
19
|
+
"terms": "grey",
|
|
20
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
21
|
+
"source": "https://developers.openai.com/codex/auth"
|
|
22
|
+
}
|
|
18
23
|
},
|
|
19
24
|
"openrouter": {
|
|
20
25
|
"pi": "openrouter",
|
|
@@ -27,7 +32,12 @@
|
|
|
27
32
|
"terms": "allowed",
|
|
28
33
|
"hidden": false,
|
|
29
34
|
"why": "Documented sign-in for any app, no registration. Pay-as-you-go credits, not a subscription.",
|
|
30
|
-
"source": "https://openrouter.ai/docs/guides/overview/auth/oauth"
|
|
35
|
+
"source": "https://openrouter.ai/docs/guides/overview/auth/oauth",
|
|
36
|
+
"multiAccount": {
|
|
37
|
+
"terms": "grey",
|
|
38
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
39
|
+
"source": "https://openrouter.ai/docs/guides/overview/auth/oauth"
|
|
40
|
+
}
|
|
31
41
|
},
|
|
32
42
|
"grok": {
|
|
33
43
|
"pi": "xai",
|
|
@@ -40,7 +50,12 @@
|
|
|
40
50
|
"terms": "partner",
|
|
41
51
|
"hidden": true,
|
|
42
52
|
"why": "xAI allows plan sign-in only in apps it has partnered with.",
|
|
43
|
-
"source": "https://x.ai/news/grok-opencode"
|
|
53
|
+
"source": "https://x.ai/news/grok-opencode",
|
|
54
|
+
"multiAccount": {
|
|
55
|
+
"terms": "grey",
|
|
56
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
57
|
+
"source": "https://x.ai/news/grok-opencode"
|
|
58
|
+
}
|
|
44
59
|
},
|
|
45
60
|
"copilot": {
|
|
46
61
|
"pi": "github-copilot",
|
|
@@ -53,6 +68,53 @@
|
|
|
53
68
|
"terms": "partner",
|
|
54
69
|
"hidden": true,
|
|
55
70
|
"why": "GitHub allows Copilot sign-in only in apps it has partnered with; this uses VS Code's client.",
|
|
56
|
-
"source": "https://github.blog/changelog/2026-01-16-github-copilot-now-supports-opencode"
|
|
71
|
+
"source": "https://github.blog/changelog/2026-01-16-github-copilot-now-supports-opencode",
|
|
72
|
+
"multiAccount": {
|
|
73
|
+
"terms": "grey",
|
|
74
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
75
|
+
"source": "https://github.blog/changelog/2026-01-16-github-copilot-now-supports-opencode"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"anthropic": {
|
|
79
|
+
"pi": "anthropic",
|
|
80
|
+
"name": "Claude",
|
|
81
|
+
"company": "Anthropic",
|
|
82
|
+
"models": {
|
|
83
|
+
"strong": "claude-opus-5-5"
|
|
84
|
+
},
|
|
85
|
+
"billing": "api",
|
|
86
|
+
"terms": "allowed",
|
|
87
|
+
"hidden": false,
|
|
88
|
+
"auth": "api-key",
|
|
89
|
+
"label": "API key (billed per use)",
|
|
90
|
+
"offer": false,
|
|
91
|
+
"why": "The app passes an Anthropic API key explicitly. API key (billed per use), never a default or subscription fallback.",
|
|
92
|
+
"source": "https://platform.claude.com/docs/en/api/overview",
|
|
93
|
+
"multiAccount": {
|
|
94
|
+
"terms": "grey",
|
|
95
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
96
|
+
"source": "https://platform.claude.com/docs/en/api/overview"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"claude": {
|
|
100
|
+
"pi": "byokit-claude-plan",
|
|
101
|
+
"name": "Claude",
|
|
102
|
+
"label": "Claude Pro/Max",
|
|
103
|
+
"company": "Anthropic",
|
|
104
|
+
"models": {
|
|
105
|
+
"strong": "claude-opus-5-5"
|
|
106
|
+
},
|
|
107
|
+
"clientId": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
|
|
108
|
+
"billing": "subscription",
|
|
109
|
+
"auth": "oauth",
|
|
110
|
+
"terms": "grey",
|
|
111
|
+
"hidden": false,
|
|
112
|
+
"why": "Anthropic prohibits third-party Claude.ai sign-in without approval; this route may stop working or lead to account restrictions.",
|
|
113
|
+
"source": "https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use",
|
|
114
|
+
"multiAccount": {
|
|
115
|
+
"terms": "grey",
|
|
116
|
+
"why": "This source does not explicitly document choosing between several accounts of this service in another app.",
|
|
117
|
+
"source": "https://code.claude.com/docs/en/legal-and-compliance#authentication-and-credential-use"
|
|
118
|
+
}
|
|
57
119
|
}
|
|
58
120
|
}
|