@rudra-js/anthropic 0.7.4 → 0.7.6

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @rudra-js/anthropic
2
2
 
3
+ This package is part of [rudra-js](https://rudrajs.com).
4
+
5
+ rudra-js is an open-source TypeScript library (MIT) you install from npm. A language model designs one product-recommendation block for a page in your shop, rudra-js checks it against your catalog, and React renders it.
6
+
7
+ It is not a hosted platform, a visual or low-code app builder, or a workspace you sign up for.
8
+
3
9
  An Anthropic adapter for
4
10
  [`@rudra-js/core`](https://github.com/clivedsouza1010/rudra-js/tree/main/packages/core)'s
5
11
  `ComponentProvider`.
@@ -118,7 +124,7 @@ anything.
118
124
  When the API answers with an error, the `Error` we throw carries the status code
119
125
  and the vendor's error category, like `anthropic responded 400
120
126
  (invalid_request_error)`. We leave the vendor's own message out. It quotes the
121
- request back, and for this framework the request can hold a shopper's search
127
+ request back, and in rudra-js the request can hold a shopper's search
122
128
  terms, which an adopter's `console.error(error)` would then write to a log. A
123
129
  test in `anthropic-provider.test.ts` puts a search term in that message and
124
130
  asserts it does not reach the thrown error.
@@ -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;IAEf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,UAAU,GAAG,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC;IAEpD,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAoCD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CA0G5F"}
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 String(typeof input);
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 onlyValue(input) {
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 isToolUseBlock(candidate) {
21
- return (typeof candidate === 'object' &&
22
- candidate !== null &&
23
- candidate['type'] === 'tool_use' &&
24
- candidate['name'] === TOOL_NAME);
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 ?? DEFAULT_MAX_TOKENS;
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 category = await errorCategory(response);
73
- throw new Error(`anthropic responded ${response.status}${category}`);
72
+ const suffix = await errorTypeSuffix(response);
73
+ throw new Error(`anthropic responded ${response.status}${suffix}`);
74
74
  }
75
- const parsed = await response.json();
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
- const cause = error instanceof Error ? (error.cause ?? error) : error;
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
- async function errorCategory(response) {
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 numberAt = (key) => {
131
- const value = usage[key];
132
- return typeof value === 'number' ? value : undefined;
133
- };
134
- const inputTokens = numberAt('input_tokens');
135
- const outputTokens = numberAt('output_tokens');
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;AAsBxB,MAAM,SAAS,GAAG,qBAAqB,CAAC;AAExC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAQhC,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,eAAe,KAAK,CAAC,MAAM,EAAE,CAAC;IAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC;IAE3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,uBAAuB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED,SAAS,cAAc,CAAC,SAAkB;IACxC,OAAO,CACL,OAAO,SAAS,KAAK,QAAQ;QAC7B,SAAS,KAAK,IAAI;QACjB,SAAqC,CAAC,MAAM,CAAC,KAAK,UAAU;QAC5D,SAAqC,CAAC,MAAM,CAAC,KAAK,SAAS,CAC7D,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,iBAAiB,CAAC;IACjD,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAmB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACpF,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAE/C,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,2BAA2B,CAAC;IAC7D,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAE1D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,KAAK;QAEL,KAAK,CAAC,QAAQ,CAAC,OAAwB;YACrC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAEhC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,GAAG,OAAO,cAAc,EAAE;gBAC1D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,WAAW,EAAE,OAAO,CAAC,MAAM;oBAC3B,mBAAmB,EAAE,YAAY;oBACjC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAClF;gBAED,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK;oBACL,UAAU,EAAE,SAAS;oBACrB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAEjC,MAAM,EAAE;wBACN,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE;wBAC5E;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,wCAAwC,SAAS,yDAAyD;yBACjH;qBACF;oBACD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;oBACnD,KAAK,EAAE;wBACL;4BACE,IAAI,EAAE,SAAS;4BACf,WAAW,EAAE,qCAAqC;4BAElD,YAAY,EAAE,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC;yBAC9D;qBACF;oBACD,uFAAuF;oBACvF,WAAW,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,yBAAyB,EAAE,IAAI,EAAE;iBAC/D,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;gBAE/C,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,MAAM,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YAE9C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CACb,sBAAsB,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,MAAM,iBAAiB,CAChF,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAIZ,CAAC;YAEF,IAAI,IAAI,CAAC,WAAW,KAAK,YAAY,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CACb,+CAA+C,SAAS,+BAA+B,CACxF,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;YACxE,CAAC;YAED,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,KAAK,CAAC,yBAAyB,SAAS,WAAW,CAAC,CAAC;YACjE,CAAC;YAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACrD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YAE1F,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CACb,wBAAwB,SAAS,0CAA0C;oBACzE,WAAW,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAC1C,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CACxB,CAAC;YACJ,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAElC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC/C,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAA6B,EAAE,GAAW,EAAE,IAAiB;IAC/E,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO;YAAE,MAAM,KAAK,CAAC;QAEtC,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACtE,MAAM,MAAM,GACV,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK;YACnD,CAAC,CAAC,MAAM,CAAE,KAA2B,CAAC,IAAI,CAAC;YAC3C,CAAC,CAAC,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAE7D,MAAM,IAAI,KAAK,CAAC,6BAA6B,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,QAAkB;IAC7C,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,IAAI,GAAI,IAAuC,EAAE,KAAK,EAAE,IAAI,CAAC;QACnE,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,KAA0C;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAsB,EAAE;QACnD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACzB,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,QAAQ,CAAC,yBAAyB,CAAC,CAAC;IAC5D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,6BAA6B,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAG;QACb,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACrD,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC;QACvD,GAAG,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC;QAC7D,GAAG,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC;KAChE,CAAC;IAEF,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,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,13 +1,15 @@
1
1
  {
2
2
  "name": "@rudra-js/anthropic",
3
- "version": "0.7.4",
4
- "description": "An Anthropic adapter for @rudra-js/core, with no vendor SDK",
3
+ "version": "0.7.6",
4
+ "description": "Anthropic adapter for rudra-js (@rudra-js/core), with no vendor SDK",
5
5
  "keywords": [
6
+ "rudra-js",
7
+ "product-recommendations",
6
8
  "anthropic",
7
9
  "claude",
8
10
  "llm",
9
- "rudra",
10
- "server-components"
11
+ "ecommerce",
12
+ "typescript"
11
13
  ],
12
14
  "license": "MIT",
13
15
  "author": "Clive Dsouza",
@@ -50,10 +52,10 @@
50
52
  "zod": "^4.0.0"
51
53
  },
52
54
  "peerDependencies": {
53
- "@rudra-js/core": "^0.7.4"
55
+ "@rudra-js/core": "^0.7.6"
54
56
  },
55
57
  "devDependencies": {
56
- "@rudra-js/core": "0.7.4",
58
+ "@rudra-js/core": "0.7.6",
57
59
  "@types/node": "^26.4.1"
58
60
  }
59
61
  }
@@ -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 String(typeof input);
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 onlyValue(input: unknown): unknown {
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 isToolUseBlock(candidate: unknown): candidate is ToolUseBlock {
49
- return (
50
- typeof candidate === 'object' &&
51
- candidate !== null &&
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 ?? DEFAULT_MAX_TOKENS;
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 category = await errorCategory(response);
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
- const body = parsed as {
125
- content?: unknown;
126
- usage?: Record<string, unknown>;
127
- stop_reason?: string;
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
- if (!block) {
143
- throw new Error(`anthropic returned no ${TOOL_NAME} tool use`);
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
- const asSent = request.schema.safeParse(block.input);
147
- const usable = asSent.success ? asSent : request.schema.safeParse(onlyValue(block.input));
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
- if (!usable.success) {
150
- throw new Error(
151
- `anthropic returned a ${TOOL_NAME} tool use that does not fit the schema. ` +
152
- `It sent ${describeShape(block.input)}.`,
153
- { cause: asSent.error },
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
- const spec = usable.data;
158
- const usage = toUsage(body.usage);
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
- return { spec, ...(usage ? { usage } : {}) };
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
- async function errorCategory(response: Response): Promise<string> {
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 numberAt = (key: string): number | undefined => {
195
- const value = usage[key];
196
- return typeof value === 'number' ? value : undefined;
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
  }