@rudra-js/anthropic 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/anthropic-provider.d.ts.map +1 -1
- package/dist/anthropic-provider.js +87 -84
- package/dist/anthropic-provider.js.map +1 -1
- package/package.json +3 -3
- package/src/anthropic-provider.ts +103 -119
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This adapter is documented at [rudrajs.com](https://rudrajs.com/docs#createAnthr
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npm install @rudra-js/anthropic @rudra-js/core
|
|
15
|
+
npm install @rudra-js/anthropic @rudra-js/core @rudra-js/attested
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```ts
|
|
@@ -85,13 +85,33 @@ or a region-specific endpoint you have.
|
|
|
85
85
|
|
|
86
86
|
What's in that request is listed under **What the model sees** in the
|
|
87
87
|
[`@rudra-js/core` README](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core#what-the-model-sees).
|
|
88
|
-
Read it before you send real shopper traffic.
|
|
89
|
-
request
|
|
90
|
-
|
|
88
|
+
Read it before you send real shopper traffic. Neither mode sends `user.id`. In
|
|
89
|
+
cohort mode, the default, the request can still be personal data: it names the
|
|
90
|
+
category that shopper leans towards most, says whether they have any history,
|
|
91
|
+
carries any `segment` you gave them as you wrote it, and lists candidates you
|
|
92
|
+
may have picked or ordered just for them. In per-shopper mode it also carries
|
|
93
|
+
the product that shopper is looking at, their current and recent searches,
|
|
94
|
+
whether they're returning, their likes, dislikes, purchases, basket, most-viewed
|
|
95
|
+
products, the categories they lean towards and interaction counts.
|
|
96
|
+
|
|
97
|
+
If your shop is in the EU or the UK, or sells to shoppers there, you're the one
|
|
98
|
+
sending personal data out. You'll need a data processing agreement with whoever
|
|
99
|
+
receives it, and a transfer mechanism if they're outside the EU or the UK, even
|
|
100
|
+
when your shop is outside them too.
|
|
101
|
+
|
|
102
|
+
Call Anthropic with a key from your own Anthropic account and the receiver is
|
|
103
|
+
Anthropic. Its data processing addendum is part of its Commercial Terms, makes
|
|
104
|
+
Anthropic your processor and includes the transfer clauses. Your shop is
|
|
105
|
+
Anthropic's customer. A proxy you run yourself that forwards to Anthropic
|
|
106
|
+
doesn't change that.
|
|
107
|
+
|
|
108
|
+
If you point `baseUrl` at a gateway or platform run by someone other than you or
|
|
109
|
+
Anthropic, its operator sees every request and the `apiKey` you pass. If that's
|
|
110
|
+
a key from your own Anthropic account, Anthropic is still your processor and the
|
|
111
|
+
operator is another one, so you need a DPA with the operator as well. If the
|
|
112
|
+
platform issued the key, Anthropic says the platform's terms govern that use:
|
|
113
|
+
you're the platform's customer, and its DPA is the one that covers you.
|
|
91
114
|
|
|
92
|
-
If your shop is in the EU or the UK, you're the one sending personal data to
|
|
93
|
-
Anthropic, so you'll need a data processing agreement with them plus a transfer
|
|
94
|
-
mechanism for the data leaving your region. Your shop is Anthropic's customer.
|
|
95
115
|
This package is a piece of code in the middle, and it _isn't_ a party to
|
|
96
116
|
anything.
|
|
97
117
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic-provider.d.ts","sourceRoot":"","sources":["../src/anthropic-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAIlB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic-provider.d.ts","sourceRoot":"","sources":["../src/anthropic-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,iBAAiB,EAIlB,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;IACpD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAqCD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CA2D5F"}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const TOOL_NAME = 'emit_component_spec';
|
|
3
|
-
const DEFAULT_MAX_TOKENS = 8192;
|
|
4
3
|
function describeShape(input) {
|
|
5
4
|
if (input === null)
|
|
6
5
|
return 'null';
|
|
7
6
|
if (Array.isArray(input))
|
|
8
7
|
return `an array of ${input.length}`;
|
|
9
8
|
if (typeof input !== 'object')
|
|
10
|
-
return
|
|
9
|
+
return typeof input;
|
|
11
10
|
const keys = Object.keys(input);
|
|
12
11
|
return keys.length === 0 ? 'an empty object' : `an object with keys ${keys.join(', ')}`;
|
|
13
12
|
}
|
|
14
|
-
function
|
|
13
|
+
function unwrapSingleKey(input) {
|
|
15
14
|
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
16
15
|
return undefined;
|
|
17
16
|
const values = Object.values(input);
|
|
18
17
|
return values.length === 1 ? values[0] : undefined;
|
|
19
18
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
function isSpecToolUse(block) {
|
|
20
|
+
if (typeof block !== 'object' || block === null)
|
|
21
|
+
return false;
|
|
22
|
+
const fields = block;
|
|
23
|
+
return fields['type'] === 'tool_use' && fields['name'] === TOOL_NAME;
|
|
25
24
|
}
|
|
26
25
|
export function createAnthropicProvider(options) {
|
|
27
26
|
const model = options.model ?? 'claude-sonnet-5';
|
|
@@ -30,76 +29,78 @@ export function createAnthropicProvider(options) {
|
|
|
30
29
|
let baseUrl = options.baseUrl ?? 'https://api.anthropic.com';
|
|
31
30
|
while (baseUrl.endsWith('/'))
|
|
32
31
|
baseUrl = baseUrl.slice(0, -1);
|
|
33
|
-
const maxTokens = options.maxTokens ??
|
|
32
|
+
const maxTokens = options.maxTokens ?? 8192;
|
|
34
33
|
return {
|
|
35
34
|
name: 'anthropic',
|
|
36
35
|
model,
|
|
37
36
|
async generate(request) {
|
|
38
37
|
request.signal.throwIfAborted();
|
|
38
|
+
const headers = {
|
|
39
|
+
'content-type': 'application/json',
|
|
40
|
+
'x-api-key': options.apiKey,
|
|
41
|
+
'anthropic-version': '2023-06-01',
|
|
42
|
+
...(options.workspaceId ? { 'anthropic-workspace-id': options.workspaceId } : {}),
|
|
43
|
+
};
|
|
44
|
+
const body = {
|
|
45
|
+
model,
|
|
46
|
+
max_tokens: maxTokens,
|
|
47
|
+
...(thinking ? { thinking } : {}),
|
|
48
|
+
system: [
|
|
49
|
+
{ type: 'text', text: request.system, cache_control: { type: 'ephemeral' } },
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
text: `Return that JSON as the input to the ${TOOL_NAME} tool, not as text. You may say a brief sentence first.`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
messages: [{ role: 'user', content: request.user }],
|
|
56
|
+
tools: [
|
|
57
|
+
{
|
|
58
|
+
name: TOOL_NAME,
|
|
59
|
+
description: 'Return the component specification.',
|
|
60
|
+
input_schema: z.toJSONSchema(request.schema, { io: 'input' }),
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
tool_choice: { type: 'auto', disable_parallel_tool_use: true },
|
|
64
|
+
};
|
|
39
65
|
const response = await send(call, `${baseUrl}/v1/messages`, {
|
|
40
66
|
method: 'POST',
|
|
41
|
-
headers
|
|
42
|
-
'content-type': 'application/json',
|
|
43
|
-
'x-api-key': options.apiKey,
|
|
44
|
-
'anthropic-version': '2023-06-01',
|
|
45
|
-
...(options.workspaceId ? { 'anthropic-workspace-id': options.workspaceId } : {}),
|
|
46
|
-
},
|
|
67
|
+
headers,
|
|
47
68
|
signal: request.signal,
|
|
48
|
-
body: JSON.stringify(
|
|
49
|
-
model,
|
|
50
|
-
max_tokens: maxTokens,
|
|
51
|
-
...(thinking ? { thinking } : {}),
|
|
52
|
-
system: [
|
|
53
|
-
{ type: 'text', text: request.system, cache_control: { type: 'ephemeral' } },
|
|
54
|
-
{
|
|
55
|
-
type: 'text',
|
|
56
|
-
text: `Return that JSON as the input to the ${TOOL_NAME} tool, not as text. You may say a brief sentence first.`,
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
messages: [{ role: 'user', content: request.user }],
|
|
60
|
-
tools: [
|
|
61
|
-
{
|
|
62
|
-
name: TOOL_NAME,
|
|
63
|
-
description: 'Return the component specification.',
|
|
64
|
-
input_schema: z.toJSONSchema(request.schema, { io: 'input' }),
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
// Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1 reject a forced tool call with a 400.
|
|
68
|
-
tool_choice: { type: 'auto', disable_parallel_tool_use: true },
|
|
69
|
-
}),
|
|
69
|
+
body: JSON.stringify(body),
|
|
70
70
|
});
|
|
71
71
|
if (!response.ok) {
|
|
72
|
-
const
|
|
73
|
-
throw new Error(`anthropic responded ${response.status}${
|
|
72
|
+
const suffix = await errorTypeSuffix(response);
|
|
73
|
+
throw new Error(`anthropic responded ${response.status}${suffix}`);
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
77
|
-
throw new Error(`anthropic returned ${parsed === null ? 'null' : typeof parsed}, not an object`);
|
|
78
|
-
}
|
|
79
|
-
const body = parsed;
|
|
80
|
-
if (body.stop_reason === 'max_tokens') {
|
|
81
|
-
throw new Error(`anthropic stopped at the max_tokens budget (${maxTokens}) before returning a tool use`);
|
|
82
|
-
}
|
|
83
|
-
if (body.stop_reason === 'refusal') {
|
|
84
|
-
throw new Error('anthropic refused to answer (stop_reason: refusal)');
|
|
85
|
-
}
|
|
86
|
-
const blocks = Array.isArray(body.content) ? body.content : [];
|
|
87
|
-
const block = blocks.find(isToolUseBlock);
|
|
88
|
-
if (!block) {
|
|
89
|
-
throw new Error(`anthropic returned no ${TOOL_NAME} tool use`);
|
|
90
|
-
}
|
|
91
|
-
const asSent = request.schema.safeParse(block.input);
|
|
92
|
-
const usable = asSent.success ? asSent : request.schema.safeParse(onlyValue(block.input));
|
|
93
|
-
if (!usable.success) {
|
|
94
|
-
throw new Error(`anthropic returned a ${TOOL_NAME} tool use that does not fit the schema. ` +
|
|
95
|
-
`It sent ${describeShape(block.input)}.`, { cause: asSent.error });
|
|
96
|
-
}
|
|
97
|
-
const spec = usable.data;
|
|
98
|
-
const usage = toUsage(body.usage);
|
|
99
|
-
return { spec, ...(usage ? { usage } : {}) };
|
|
75
|
+
return readSpec(await response.json(), request.schema, maxTokens);
|
|
100
76
|
},
|
|
101
77
|
};
|
|
102
78
|
}
|
|
79
|
+
function readSpec(reply, schema, maxTokens) {
|
|
80
|
+
if (typeof reply !== 'object' || reply === null) {
|
|
81
|
+
throw new Error(`anthropic returned ${reply === null ? 'null' : typeof reply}, not an object`);
|
|
82
|
+
}
|
|
83
|
+
const { stop_reason: stopReason, content, usage } = reply;
|
|
84
|
+
if (stopReason === 'max_tokens') {
|
|
85
|
+
throw new Error(`anthropic stopped at the max_tokens budget (${maxTokens}) before returning a tool use`);
|
|
86
|
+
}
|
|
87
|
+
if (stopReason === 'refusal') {
|
|
88
|
+
throw new Error('anthropic refused to answer (stop_reason: refusal)');
|
|
89
|
+
}
|
|
90
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
91
|
+
const block = blocks.find(isSpecToolUse);
|
|
92
|
+
if (!block) {
|
|
93
|
+
throw new Error(`anthropic returned no ${TOOL_NAME} tool use`);
|
|
94
|
+
}
|
|
95
|
+
const asSent = schema.safeParse(block.input);
|
|
96
|
+
const result = asSent.success ? asSent : schema.safeParse(unwrapSingleKey(block.input));
|
|
97
|
+
if (!result.success) {
|
|
98
|
+
throw new Error(`anthropic returned a ${TOOL_NAME} tool use that does not fit the schema. ` +
|
|
99
|
+
`It sent ${describeShape(block.input)}.`, { cause: asSent.error });
|
|
100
|
+
}
|
|
101
|
+
const tokenUsage = toUsage(usage);
|
|
102
|
+
return tokenUsage ? { spec: result.data, usage: tokenUsage } : { spec: result.data };
|
|
103
|
+
}
|
|
103
104
|
async function send(call, url, init) {
|
|
104
105
|
try {
|
|
105
106
|
return await call(url, init);
|
|
@@ -107,14 +108,18 @@ async function send(call, url, init) {
|
|
|
107
108
|
catch (error) {
|
|
108
109
|
if (init.signal?.aborted)
|
|
109
110
|
throw error;
|
|
110
|
-
|
|
111
|
-
const detail = cause && typeof cause === 'object' && 'code' in cause
|
|
112
|
-
? String(cause.code)
|
|
113
|
-
: String(cause instanceof Error ? cause.message : cause);
|
|
114
|
-
throw new Error(`anthropic did not answer: ${detail}`, { cause: error });
|
|
111
|
+
throw new Error(`anthropic did not answer: ${transportFault(error)}`, { cause: error });
|
|
115
112
|
}
|
|
116
113
|
}
|
|
117
|
-
|
|
114
|
+
function transportFault(error) {
|
|
115
|
+
const cause = error instanceof Error ? (error.cause ?? error) : error;
|
|
116
|
+
if (typeof cause === 'object' && cause !== null && 'code' in cause)
|
|
117
|
+
return String(cause.code);
|
|
118
|
+
if (cause instanceof Error)
|
|
119
|
+
return String(cause.message);
|
|
120
|
+
return String(cause);
|
|
121
|
+
}
|
|
122
|
+
async function errorTypeSuffix(response) {
|
|
118
123
|
try {
|
|
119
124
|
const body = JSON.parse(await response.text());
|
|
120
125
|
const type = body?.error?.type;
|
|
@@ -124,23 +129,21 @@ async function errorCategory(response) {
|
|
|
124
129
|
return '';
|
|
125
130
|
}
|
|
126
131
|
}
|
|
132
|
+
const USAGE_FIELDS = [
|
|
133
|
+
['input_tokens', 'inputTokens'],
|
|
134
|
+
['output_tokens', 'outputTokens'],
|
|
135
|
+
['cache_read_input_tokens', 'cacheReadTokens'],
|
|
136
|
+
['cache_creation_input_tokens', 'cacheWriteTokens'],
|
|
137
|
+
];
|
|
127
138
|
function toUsage(usage) {
|
|
128
139
|
if (!usage)
|
|
129
140
|
return undefined;
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const cacheReadTokens = numberAt('cache_read_input_tokens');
|
|
137
|
-
const cacheWriteTokens = numberAt('cache_creation_input_tokens');
|
|
138
|
-
const mapped = {
|
|
139
|
-
...(inputTokens === undefined ? {} : { inputTokens }),
|
|
140
|
-
...(outputTokens === undefined ? {} : { outputTokens }),
|
|
141
|
-
...(cacheReadTokens === undefined ? {} : { cacheReadTokens }),
|
|
142
|
-
...(cacheWriteTokens === undefined ? {} : { cacheWriteTokens }),
|
|
143
|
-
};
|
|
141
|
+
const mapped = {};
|
|
142
|
+
for (const [from, to] of USAGE_FIELDS) {
|
|
143
|
+
const value = usage[from];
|
|
144
|
+
if (typeof value === 'number')
|
|
145
|
+
mapped[to] = value;
|
|
146
|
+
}
|
|
144
147
|
return Object.keys(mapped).length > 0 ? mapped : undefined;
|
|
145
148
|
}
|
|
146
149
|
//# sourceMappingURL=anthropic-provider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"anthropic-provider.js","sourceRoot":"","sources":["../src/anthropic-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"anthropic-provider.js","sourceRoot":"","sources":["../src/anthropic-provider.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rudra-js/anthropic",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "An Anthropic adapter for @rudra-js/core, with no vendor SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anthropic",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"zod": "^4.0.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@rudra-js/core": "^0.7.
|
|
53
|
+
"@rudra-js/core": "^0.7.5"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@rudra-js/core": "0.7.
|
|
56
|
+
"@rudra-js/core": "0.7.5",
|
|
57
57
|
"@types/node": "^26.4.1"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -8,50 +8,47 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
export interface AnthropicProviderOptions {
|
|
10
10
|
apiKey: string;
|
|
11
|
-
|
|
12
11
|
model?: string;
|
|
13
12
|
maxTokens?: number;
|
|
14
13
|
baseUrl?: string;
|
|
15
|
-
|
|
16
14
|
workspaceId?: string;
|
|
17
|
-
|
|
18
15
|
thinking?: { type: 'adaptive' | 'disabled' } | null;
|
|
19
|
-
|
|
20
16
|
fetch?: typeof globalThis.fetch;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
19
|
const TOOL_NAME = 'emit_component_spec';
|
|
24
20
|
|
|
25
|
-
const DEFAULT_MAX_TOKENS = 8192;
|
|
26
|
-
|
|
27
21
|
interface ToolUseBlock {
|
|
28
22
|
type: 'tool_use';
|
|
29
23
|
name: string;
|
|
30
24
|
input: unknown;
|
|
31
25
|
}
|
|
32
26
|
|
|
27
|
+
interface MessagesReply {
|
|
28
|
+
stop_reason?: string;
|
|
29
|
+
content?: unknown;
|
|
30
|
+
usage?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
33
|
function describeShape(input: unknown): string {
|
|
34
34
|
if (input === null) return 'null';
|
|
35
35
|
if (Array.isArray(input)) return `an array of ${input.length}`;
|
|
36
|
-
if (typeof input !== 'object') return
|
|
36
|
+
if (typeof input !== 'object') return typeof input;
|
|
37
37
|
|
|
38
38
|
const keys = Object.keys(input);
|
|
39
39
|
return keys.length === 0 ? 'an empty object' : `an object with keys ${keys.join(', ')}`;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
function
|
|
42
|
+
function unwrapSingleKey(input: unknown): unknown {
|
|
43
43
|
if (typeof input !== 'object' || input === null || Array.isArray(input)) return undefined;
|
|
44
44
|
const values = Object.values(input);
|
|
45
45
|
return values.length === 1 ? values[0] : undefined;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
(candidate as Record<string, unknown>)['type'] === 'tool_use' &&
|
|
53
|
-
(candidate as Record<string, unknown>)['name'] === TOOL_NAME
|
|
54
|
-
);
|
|
48
|
+
function isSpecToolUse(block: unknown): block is ToolUseBlock {
|
|
49
|
+
if (typeof block !== 'object' || block === null) return false;
|
|
50
|
+
const fields = block as Record<string, unknown>;
|
|
51
|
+
return fields['type'] === 'tool_use' && fields['name'] === TOOL_NAME;
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
export function createAnthropicProvider(options: AnthropicProviderOptions): ComponentProvider {
|
|
@@ -59,10 +56,9 @@ export function createAnthropicProvider(options: AnthropicProviderOptions): Comp
|
|
|
59
56
|
const thinking =
|
|
60
57
|
options.thinking === undefined ? { type: 'disabled' as const } : options.thinking;
|
|
61
58
|
const call = options.fetch ?? globalThis.fetch;
|
|
62
|
-
|
|
63
59
|
let baseUrl = options.baseUrl ?? 'https://api.anthropic.com';
|
|
64
60
|
while (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1);
|
|
65
|
-
const maxTokens = options.maxTokens ??
|
|
61
|
+
const maxTokens = options.maxTokens ?? 8192;
|
|
66
62
|
|
|
67
63
|
return {
|
|
68
64
|
name: 'anthropic',
|
|
@@ -71,95 +67,88 @@ export function createAnthropicProvider(options: AnthropicProviderOptions): Comp
|
|
|
71
67
|
async generate(request: ProviderRequest): Promise<ProviderResult> {
|
|
72
68
|
request.signal.throwIfAborted();
|
|
73
69
|
|
|
70
|
+
const headers = {
|
|
71
|
+
'content-type': 'application/json',
|
|
72
|
+
'x-api-key': options.apiKey,
|
|
73
|
+
'anthropic-version': '2023-06-01',
|
|
74
|
+
...(options.workspaceId ? { 'anthropic-workspace-id': options.workspaceId } : {}),
|
|
75
|
+
};
|
|
76
|
+
const body = {
|
|
77
|
+
model,
|
|
78
|
+
max_tokens: maxTokens,
|
|
79
|
+
...(thinking ? { thinking } : {}),
|
|
80
|
+
system: [
|
|
81
|
+
{ type: 'text', text: request.system, cache_control: { type: 'ephemeral' } },
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: `Return that JSON as the input to the ${TOOL_NAME} tool, not as text. You may say a brief sentence first.`,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
messages: [{ role: 'user', content: request.user }],
|
|
88
|
+
tools: [
|
|
89
|
+
{
|
|
90
|
+
name: TOOL_NAME,
|
|
91
|
+
description: 'Return the component specification.',
|
|
92
|
+
input_schema: z.toJSONSchema(request.schema, { io: 'input' }),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
tool_choice: { type: 'auto', disable_parallel_tool_use: true },
|
|
96
|
+
};
|
|
97
|
+
|
|
74
98
|
const response = await send(call, `${baseUrl}/v1/messages`, {
|
|
75
99
|
method: 'POST',
|
|
76
|
-
headers
|
|
77
|
-
'content-type': 'application/json',
|
|
78
|
-
'x-api-key': options.apiKey,
|
|
79
|
-
'anthropic-version': '2023-06-01',
|
|
80
|
-
...(options.workspaceId ? { 'anthropic-workspace-id': options.workspaceId } : {}),
|
|
81
|
-
},
|
|
82
|
-
|
|
100
|
+
headers,
|
|
83
101
|
signal: request.signal,
|
|
84
|
-
body: JSON.stringify(
|
|
85
|
-
model,
|
|
86
|
-
max_tokens: maxTokens,
|
|
87
|
-
...(thinking ? { thinking } : {}),
|
|
88
|
-
|
|
89
|
-
system: [
|
|
90
|
-
{ type: 'text', text: request.system, cache_control: { type: 'ephemeral' } },
|
|
91
|
-
{
|
|
92
|
-
type: 'text',
|
|
93
|
-
text: `Return that JSON as the input to the ${TOOL_NAME} tool, not as text. You may say a brief sentence first.`,
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
messages: [{ role: 'user', content: request.user }],
|
|
97
|
-
tools: [
|
|
98
|
-
{
|
|
99
|
-
name: TOOL_NAME,
|
|
100
|
-
description: 'Return the component specification.',
|
|
101
|
-
|
|
102
|
-
input_schema: z.toJSONSchema(request.schema, { io: 'input' }),
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
// Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1 reject a forced tool call with a 400.
|
|
106
|
-
tool_choice: { type: 'auto', disable_parallel_tool_use: true },
|
|
107
|
-
}),
|
|
102
|
+
body: JSON.stringify(body),
|
|
108
103
|
});
|
|
109
104
|
|
|
110
105
|
if (!response.ok) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
throw new Error(`anthropic responded ${response.status}${category}`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const parsed: unknown = await response.json();
|
|
117
|
-
|
|
118
|
-
if (typeof parsed !== 'object' || parsed === null) {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`anthropic returned ${parsed === null ? 'null' : typeof parsed}, not an object`,
|
|
121
|
-
);
|
|
106
|
+
const suffix = await errorTypeSuffix(response);
|
|
107
|
+
throw new Error(`anthropic responded ${response.status}${suffix}`);
|
|
122
108
|
}
|
|
123
109
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
if (body.stop_reason === 'max_tokens') {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`anthropic stopped at the max_tokens budget (${maxTokens}) before returning a tool use`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
if (body.stop_reason === 'refusal') {
|
|
136
|
-
throw new Error('anthropic refused to answer (stop_reason: refusal)');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const blocks = Array.isArray(body.content) ? body.content : [];
|
|
140
|
-
const block = blocks.find(isToolUseBlock);
|
|
110
|
+
return readSpec(await response.json(), request.schema, maxTokens);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
141
114
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
115
|
+
function readSpec(
|
|
116
|
+
reply: unknown,
|
|
117
|
+
schema: ProviderRequest['schema'],
|
|
118
|
+
maxTokens: number,
|
|
119
|
+
): ProviderResult {
|
|
120
|
+
if (typeof reply !== 'object' || reply === null) {
|
|
121
|
+
throw new Error(`anthropic returned ${reply === null ? 'null' : typeof reply}, not an object`);
|
|
122
|
+
}
|
|
145
123
|
|
|
146
|
-
|
|
147
|
-
|
|
124
|
+
const { stop_reason: stopReason, content, usage } = reply as MessagesReply;
|
|
125
|
+
if (stopReason === 'max_tokens') {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`anthropic stopped at the max_tokens budget (${maxTokens}) before returning a tool use`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (stopReason === 'refusal') {
|
|
131
|
+
throw new Error('anthropic refused to answer (stop_reason: refusal)');
|
|
132
|
+
}
|
|
148
133
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
);
|
|
155
|
-
}
|
|
134
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
135
|
+
const block = blocks.find(isSpecToolUse);
|
|
136
|
+
if (!block) {
|
|
137
|
+
throw new Error(`anthropic returned no ${TOOL_NAME} tool use`);
|
|
138
|
+
}
|
|
156
139
|
|
|
157
|
-
|
|
158
|
-
|
|
140
|
+
const asSent = schema.safeParse(block.input);
|
|
141
|
+
const result = asSent.success ? asSent : schema.safeParse(unwrapSingleKey(block.input));
|
|
142
|
+
if (!result.success) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`anthropic returned a ${TOOL_NAME} tool use that does not fit the schema. ` +
|
|
145
|
+
`It sent ${describeShape(block.input)}.`,
|
|
146
|
+
{ cause: asSent.error },
|
|
147
|
+
);
|
|
148
|
+
}
|
|
159
149
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
};
|
|
150
|
+
const tokenUsage = toUsage(usage);
|
|
151
|
+
return tokenUsage ? { spec: result.data, usage: tokenUsage } : { spec: result.data };
|
|
163
152
|
}
|
|
164
153
|
|
|
165
154
|
async function send(call: typeof globalThis.fetch, url: string, init: RequestInit) {
|
|
@@ -167,18 +156,18 @@ async function send(call: typeof globalThis.fetch, url: string, init: RequestIni
|
|
|
167
156
|
return await call(url, init);
|
|
168
157
|
} catch (error) {
|
|
169
158
|
if (init.signal?.aborted) throw error;
|
|
170
|
-
|
|
171
|
-
const cause = error instanceof Error ? (error.cause ?? error) : error;
|
|
172
|
-
const detail =
|
|
173
|
-
cause && typeof cause === 'object' && 'code' in cause
|
|
174
|
-
? String((cause as { code: unknown }).code)
|
|
175
|
-
: String(cause instanceof Error ? cause.message : cause);
|
|
176
|
-
|
|
177
|
-
throw new Error(`anthropic did not answer: ${detail}`, { cause: error });
|
|
159
|
+
throw new Error(`anthropic did not answer: ${transportFault(error)}`, { cause: error });
|
|
178
160
|
}
|
|
179
161
|
}
|
|
180
162
|
|
|
181
|
-
|
|
163
|
+
function transportFault(error: unknown): string {
|
|
164
|
+
const cause = error instanceof Error ? (error.cause ?? error) : error;
|
|
165
|
+
if (typeof cause === 'object' && cause !== null && 'code' in cause) return String(cause.code);
|
|
166
|
+
if (cause instanceof Error) return String(cause.message);
|
|
167
|
+
return String(cause);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function errorTypeSuffix(response: Response): Promise<string> {
|
|
182
171
|
try {
|
|
183
172
|
const body: unknown = JSON.parse(await response.text());
|
|
184
173
|
const type = (body as { error?: { type?: unknown } })?.error?.type;
|
|
@@ -188,25 +177,20 @@ async function errorCategory(response: Response): Promise<string> {
|
|
|
188
177
|
}
|
|
189
178
|
}
|
|
190
179
|
|
|
180
|
+
const USAGE_FIELDS = [
|
|
181
|
+
['input_tokens', 'inputTokens'],
|
|
182
|
+
['output_tokens', 'outputTokens'],
|
|
183
|
+
['cache_read_input_tokens', 'cacheReadTokens'],
|
|
184
|
+
['cache_creation_input_tokens', 'cacheWriteTokens'],
|
|
185
|
+
] as const;
|
|
186
|
+
|
|
191
187
|
function toUsage(usage: Record<string, unknown> | undefined): TokenUsage | undefined {
|
|
192
188
|
if (!usage) return undefined;
|
|
193
189
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const inputTokens = numberAt('input_tokens');
|
|
200
|
-
const outputTokens = numberAt('output_tokens');
|
|
201
|
-
const cacheReadTokens = numberAt('cache_read_input_tokens');
|
|
202
|
-
const cacheWriteTokens = numberAt('cache_creation_input_tokens');
|
|
203
|
-
|
|
204
|
-
const mapped = {
|
|
205
|
-
...(inputTokens === undefined ? {} : { inputTokens }),
|
|
206
|
-
...(outputTokens === undefined ? {} : { outputTokens }),
|
|
207
|
-
...(cacheReadTokens === undefined ? {} : { cacheReadTokens }),
|
|
208
|
-
...(cacheWriteTokens === undefined ? {} : { cacheWriteTokens }),
|
|
209
|
-
};
|
|
210
|
-
|
|
190
|
+
const mapped: TokenUsage = {};
|
|
191
|
+
for (const [from, to] of USAGE_FIELDS) {
|
|
192
|
+
const value = usage[from];
|
|
193
|
+
if (typeof value === 'number') mapped[to] = value;
|
|
194
|
+
}
|
|
211
195
|
return Object.keys(mapped).length > 0 ? mapped : undefined;
|
|
212
196
|
}
|