@namzu/sdk 0.1.6 → 0.1.7

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/constants/provider/index.d.ts +0 -3
  3. package/dist/constants/provider/index.d.ts.map +1 -1
  4. package/dist/constants/provider/index.js +0 -18
  5. package/dist/constants/provider/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/provider/__tests__/registry.test.d.ts +11 -0
  11. package/dist/provider/__tests__/registry.test.d.ts.map +1 -0
  12. package/dist/provider/__tests__/registry.test.js +118 -0
  13. package/dist/provider/__tests__/registry.test.js.map +1 -0
  14. package/dist/provider/index.d.ts +3 -3
  15. package/dist/provider/index.d.ts.map +1 -1
  16. package/dist/provider/index.js +3 -3
  17. package/dist/provider/index.js.map +1 -1
  18. package/dist/provider/mock-register.d.ts +12 -0
  19. package/dist/provider/mock-register.d.ts.map +1 -0
  20. package/dist/provider/mock-register.js +24 -0
  21. package/dist/provider/mock-register.js.map +1 -0
  22. package/dist/provider/mock.d.ts +26 -0
  23. package/dist/provider/mock.d.ts.map +1 -0
  24. package/dist/provider/{factory.js → mock.js} +3 -45
  25. package/dist/provider/mock.js.map +1 -0
  26. package/dist/provider/registry.d.ts +47 -0
  27. package/dist/provider/registry.d.ts.map +1 -0
  28. package/dist/provider/registry.js +89 -0
  29. package/dist/provider/registry.js.map +1 -0
  30. package/dist/rag/rag-tool.d.ts +1 -1
  31. package/dist/types/provider/config.d.ts +29 -21
  32. package/dist/types/provider/config.d.ts.map +1 -1
  33. package/dist/types/provider/index.d.ts +1 -1
  34. package/dist/types/provider/index.d.ts.map +1 -1
  35. package/package.json +4 -2
  36. package/src/constants/provider/index.ts +0 -22
  37. package/src/index.ts +5 -4
  38. package/src/provider/__tests__/registry.test.ts +155 -0
  39. package/src/provider/index.ts +3 -3
  40. package/src/provider/mock-register.ts +27 -0
  41. package/src/provider/{factory.ts → mock.ts} +2 -57
  42. package/src/provider/registry.ts +118 -0
  43. package/src/types/provider/config.ts +31 -29
  44. package/src/types/provider/index.ts +3 -4
  45. package/dist/provider/bedrock/client.d.ts +0 -14
  46. package/dist/provider/bedrock/client.d.ts.map +0 -1
  47. package/dist/provider/bedrock/client.js +0 -460
  48. package/dist/provider/bedrock/client.js.map +0 -1
  49. package/dist/provider/factory.d.ts +0 -39
  50. package/dist/provider/factory.d.ts.map +0 -1
  51. package/dist/provider/factory.js.map +0 -1
  52. package/dist/provider/openrouter/client.d.ts +0 -17
  53. package/dist/provider/openrouter/client.d.ts.map +0 -1
  54. package/dist/provider/openrouter/client.js +0 -305
  55. package/dist/provider/openrouter/client.js.map +0 -1
  56. package/src/provider/bedrock/client.ts +0 -548
  57. package/src/provider/openrouter/client.ts +0 -390
@@ -1,548 +0,0 @@
1
- import {
2
- BedrockRuntimeClient,
3
- ConverseCommand,
4
- ConverseStreamCommand,
5
- } from '@aws-sdk/client-bedrock-runtime'
6
- import type {
7
- Message as BedrockMessage,
8
- ContentBlock,
9
- ConversationRole,
10
- ConverseStreamOutput,
11
- SystemContentBlock,
12
- Tool,
13
- ToolConfiguration,
14
- ToolResultContentBlock,
15
- ToolUseBlock,
16
- } from '@aws-sdk/client-bedrock-runtime'
17
- import { SpanStatusCode } from '@opentelemetry/api'
18
- import { GENAI, NAMZU, chatSpanName } from '../../telemetry/attributes.js'
19
- import type { TokenUsage } from '../../types/common/index.js'
20
- import type {
21
- ChatCompletionParams,
22
- ChatCompletionResponse,
23
- LLMProvider,
24
- ModelInfo,
25
- StreamChunk,
26
- ToolChoice,
27
- } from '../../types/provider/index.js'
28
- import type { BedrockConfig } from '../../types/provider/index.js'
29
- import { toErrorMessage } from '../../utils/error.js'
30
- import { getRootLogger } from '../../utils/logger.js'
31
- import { getTracer } from '../telemetry/setup.js'
32
-
33
- const logger = getRootLogger().child({ component: 'BedrockProvider' })
34
-
35
- function extractSystemBlocks(messages: ChatCompletionParams['messages']): SystemContentBlock[] {
36
- return messages
37
- .filter((m) => m.role === 'system')
38
- .map((m) => ({ text: typeof m.content === 'string' ? m.content : '' }))
39
- }
40
-
41
- function toBedrockRole(role: string): ConversationRole {
42
- return role === 'assistant' ? 'assistant' : 'user'
43
- }
44
-
45
- function toBedrockMessages(messages: ChatCompletionParams['messages']): BedrockMessage[] {
46
- const out: BedrockMessage[] = []
47
-
48
- let pendingToolResults: ContentBlock[] = []
49
-
50
- const flushToolResults = () => {
51
- if (pendingToolResults.length > 0) {
52
- out.push({ role: 'user', content: pendingToolResults })
53
- pendingToolResults = []
54
- }
55
- }
56
-
57
- for (const msg of messages) {
58
- if (msg.role === 'system') continue
59
-
60
- if (msg.role === 'tool') {
61
- const toolMsg = msg as { toolCallId?: string; content?: string }
62
- const resultBlock: ToolResultContentBlock = {
63
- text:
64
- typeof toolMsg.content === 'string' ? toolMsg.content : JSON.stringify(toolMsg.content),
65
- }
66
- pendingToolResults.push({
67
- toolResult: {
68
- toolUseId: toolMsg.toolCallId ?? 'unknown',
69
- content: [resultBlock],
70
- },
71
- })
72
- continue
73
- }
74
-
75
- flushToolResults()
76
-
77
- if (msg.role === 'assistant' && 'toolCalls' in msg && msg.toolCalls) {
78
- const content: ContentBlock[] = []
79
- if (msg.content) {
80
- content.push({ text: msg.content })
81
- }
82
- for (const tc of msg.toolCalls) {
83
- content.push({
84
- toolUse: {
85
- toolUseId: tc.id,
86
- name: tc.function.name,
87
- input: JSON.parse(tc.function.arguments || '{}'),
88
- },
89
- })
90
- }
91
- out.push({ role: 'assistant', content })
92
- continue
93
- }
94
-
95
- const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
96
- out.push({
97
- role: toBedrockRole(msg.role),
98
- content: [{ text }],
99
- })
100
- }
101
-
102
- flushToolResults()
103
-
104
- return out
105
- }
106
-
107
- function messagesContainToolBlocks(messages: ChatCompletionParams['messages']): boolean {
108
- for (const msg of messages) {
109
- if (msg.role === 'tool') return true
110
- if (
111
- msg.role === 'assistant' &&
112
- 'toolCalls' in msg &&
113
- msg.toolCalls &&
114
- msg.toolCalls.length > 0
115
- ) {
116
- return true
117
- }
118
- }
119
- return false
120
- }
121
-
122
- function extractToolNamesFromHistory(messages: ChatCompletionParams['messages']): string[] {
123
- const names = new Set<string>()
124
- for (const msg of messages) {
125
- if (msg.role === 'assistant' && 'toolCalls' in msg && msg.toolCalls) {
126
- for (const tc of msg.toolCalls) {
127
- names.add(tc.function.name)
128
- }
129
- }
130
- }
131
- return Array.from(names)
132
- }
133
-
134
- function toBedrockToolConfig(params: ChatCompletionParams): ToolConfiguration | undefined {
135
- if (params.tools && params.tools.length > 0) {
136
- const tools: Tool[] = params.tools.map(
137
- (t) =>
138
- ({
139
- toolSpec: {
140
- name: t.function.name,
141
- description: t.function.description ?? '',
142
- inputSchema: {
143
- json: (t.function.parameters ?? {}) as Record<string, unknown>,
144
- },
145
- },
146
- }) as Tool,
147
- )
148
-
149
- const toolChoice = formatToolChoice(params.toolChoice)
150
- return { tools, toolChoice }
151
- }
152
-
153
- if (messagesContainToolBlocks(params.messages)) {
154
- const toolNames = extractToolNamesFromHistory(params.messages)
155
- if (toolNames.length > 0) {
156
- const tools: Tool[] = toolNames.map(
157
- (name) =>
158
- ({
159
- toolSpec: {
160
- name,
161
- description: '(completed)',
162
- inputSchema: { json: { type: 'object' } },
163
- },
164
- }) as Tool,
165
- )
166
- return { tools, toolChoice: { auto: {} } }
167
- }
168
- }
169
-
170
- return undefined
171
- }
172
-
173
- function formatToolChoice(tc?: ToolChoice) {
174
- if (!tc || tc === 'auto') return { auto: {} }
175
- if (tc === 'none') return { auto: {} }
176
- if (tc === 'required') return { any: {} }
177
- if (typeof tc === 'object' && tc.type === 'function') {
178
- return { tool: { name: tc.function.name } }
179
- }
180
- return { auto: {} }
181
- }
182
-
183
- interface RawBedrockUsage {
184
- inputTokens?: number
185
- outputTokens?: number
186
- totalTokens?: number
187
- cacheReadInputTokenCount?: number
188
- cacheWriteInputTokenCount?: number
189
- }
190
-
191
- function parseUsage(raw?: RawBedrockUsage): TokenUsage {
192
- if (!raw) {
193
- return {
194
- promptTokens: 0,
195
- completionTokens: 0,
196
- totalTokens: 0,
197
- cachedTokens: 0,
198
- cacheWriteTokens: 0,
199
- }
200
- }
201
- const input = raw.inputTokens ?? 0
202
- const output = raw.outputTokens ?? 0
203
- return {
204
- promptTokens: input,
205
- completionTokens: output,
206
- totalTokens: raw.totalTokens ?? input + output,
207
- cachedTokens: raw.cacheReadInputTokenCount ?? 0,
208
- cacheWriteTokens: raw.cacheWriteInputTokenCount ?? 0,
209
- }
210
- }
211
-
212
- type NamzuFinishReason = ChatCompletionResponse['finishReason']
213
-
214
- function mapStopReason(reason?: string): NamzuFinishReason {
215
- switch (reason) {
216
- case 'end_turn':
217
- case 'stop_sequence':
218
- return 'stop'
219
- case 'tool_use':
220
- return 'tool_calls'
221
- case 'max_tokens':
222
- return 'length'
223
- case 'content_filtered':
224
- return 'content_filter'
225
- default:
226
- return 'stop'
227
- }
228
- }
229
-
230
- function extractResponseContent(content?: ContentBlock[]): {
231
- text: string | null
232
- toolCalls: ChatCompletionResponse['message']['toolCalls']
233
- } {
234
- if (!content || content.length === 0) return { text: null, toolCalls: undefined }
235
-
236
- let text: string | null = null
237
- const toolCalls: NonNullable<ChatCompletionResponse['message']['toolCalls']> = []
238
-
239
- for (const block of content) {
240
- if ('text' in block && block.text) {
241
- text = (text ?? '') + block.text
242
- }
243
- if ('toolUse' in block && block.toolUse) {
244
- const tu = block.toolUse as ToolUseBlock
245
- toolCalls.push({
246
- id: tu.toolUseId ?? `tool-${Date.now()}`,
247
- type: 'function',
248
- function: {
249
- name: tu.name ?? '',
250
- arguments: JSON.stringify(tu.input ?? {}),
251
- },
252
- })
253
- }
254
- }
255
-
256
- return {
257
- text,
258
- toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
259
- }
260
- }
261
-
262
- export class BedrockProvider implements LLMProvider {
263
- readonly id = 'bedrock'
264
- readonly name = 'AWS Bedrock'
265
-
266
- private client: BedrockRuntimeClient
267
- private config: BedrockConfig
268
-
269
- constructor(config: BedrockConfig) {
270
- this.config = config
271
-
272
- const clientConfig: Record<string, unknown> = {}
273
-
274
- if (config.region) {
275
- clientConfig.region = config.region
276
- }
277
-
278
- if (config.accessKeyId && config.secretAccessKey) {
279
- clientConfig.credentials = {
280
- accessKeyId: config.accessKeyId,
281
- secretAccessKey: config.secretAccessKey,
282
- ...(config.sessionToken ? { sessionToken: config.sessionToken } : {}),
283
- }
284
- }
285
-
286
- this.client = new BedrockRuntimeClient(clientConfig)
287
- }
288
-
289
- async chat(params: ChatCompletionParams): Promise<ChatCompletionResponse> {
290
- const tracer = getTracer()
291
-
292
- return tracer.startActiveSpan(chatSpanName(params.model), async (span) => {
293
- span.setAttributes({
294
- [GENAI.OPERATION_NAME]: 'chat',
295
- [GENAI.SYSTEM]: 'bedrock',
296
- [GENAI.REQUEST_MODEL]: params.model,
297
- })
298
- if (params.temperature !== undefined) {
299
- span.setAttribute(GENAI.REQUEST_TEMPERATURE, params.temperature)
300
- }
301
- if (params.maxTokens !== undefined) {
302
- span.setAttribute(GENAI.REQUEST_MAX_TOKENS, params.maxTokens)
303
- }
304
-
305
- try {
306
- const system = extractSystemBlocks(params.messages)
307
- const messages = toBedrockMessages(params.messages)
308
- const toolConfig = toBedrockToolConfig(params)
309
-
310
- const inferenceConfig: Record<string, unknown> = {}
311
- if (params.maxTokens !== undefined) inferenceConfig.maxTokens = params.maxTokens
312
- if (params.temperature !== undefined) inferenceConfig.temperature = params.temperature
313
- if (params.topP !== undefined) inferenceConfig.topP = params.topP
314
- if (params.stop) inferenceConfig.stopSequences = params.stop
315
-
316
- logger.debug('Sending Bedrock Converse request', {
317
- model: params.model,
318
- })
319
-
320
- const command = new ConverseCommand({
321
- modelId: params.model,
322
- system: system.length > 0 ? system : undefined,
323
- messages,
324
- toolConfig,
325
- inferenceConfig,
326
- })
327
-
328
- const response = await this.client.send(command, {
329
- requestTimeout: this.config.timeout ?? 120_000,
330
- })
331
-
332
- const { text, toolCalls } = extractResponseContent(response.output?.message?.content)
333
- const usage = parseUsage(response.usage as RawBedrockUsage | undefined)
334
- const finishReason = mapStopReason(response.stopReason)
335
-
336
- const requestId = response.$metadata.requestId ?? `bedrock-${Date.now()}`
337
-
338
- const result: ChatCompletionResponse = {
339
- id: requestId,
340
- model: params.model,
341
- message: {
342
- role: 'assistant',
343
- content: text,
344
- toolCalls,
345
- },
346
- finishReason,
347
- usage,
348
- }
349
-
350
- span.setAttributes({
351
- [GENAI.RESPONSE_ID]: requestId,
352
- [GENAI.RESPONSE_MODEL]: params.model,
353
- [GENAI.RESPONSE_FINISH_REASONS]: finishReason,
354
- [GENAI.USAGE_INPUT_TOKENS]: usage.promptTokens,
355
- [GENAI.USAGE_OUTPUT_TOKENS]: usage.completionTokens,
356
- [NAMZU.CACHE_READ_TOKENS]: usage.cachedTokens,
357
- [NAMZU.CACHE_WRITE_TOKENS]: usage.cacheWriteTokens,
358
- })
359
- span.setStatus({ code: SpanStatusCode.OK })
360
-
361
- return result
362
- } catch (err) {
363
- span.setStatus({
364
- code: SpanStatusCode.ERROR,
365
- message: toErrorMessage(err),
366
- })
367
- span.recordException(err instanceof Error ? err : new Error(String(err)))
368
- throw err
369
- } finally {
370
- span.end()
371
- }
372
- })
373
- }
374
-
375
- async *chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
376
- const system = extractSystemBlocks(params.messages)
377
- const messages = toBedrockMessages(params.messages)
378
- const toolConfig = toBedrockToolConfig(params)
379
-
380
- const inferenceConfig: Record<string, unknown> = {}
381
- if (params.maxTokens !== undefined) inferenceConfig.maxTokens = params.maxTokens
382
- if (params.temperature !== undefined) inferenceConfig.temperature = params.temperature
383
- if (params.topP !== undefined) inferenceConfig.topP = params.topP
384
- if (params.stop) inferenceConfig.stopSequences = params.stop
385
-
386
- const command = new ConverseStreamCommand({
387
- modelId: params.model,
388
- system: system.length > 0 ? system : undefined,
389
- messages,
390
- toolConfig,
391
- inferenceConfig,
392
- })
393
-
394
- const response = await this.client.send(command, {
395
- requestTimeout: this.config.timeout ?? 120_000,
396
- })
397
-
398
- if (!response.stream) {
399
- throw new Error('Bedrock returned no stream body')
400
- }
401
-
402
- const requestId = response.$metadata.requestId ?? `bedrock-${Date.now()}`
403
-
404
- const activeToolCalls = new Map<number, { id: string; name: string; args: string }>()
405
- let toolCallIndex = 0
406
-
407
- for await (const event of response.stream as AsyncIterable<ConverseStreamOutput>) {
408
- try {
409
- if ('contentBlockDelta' in event && event.contentBlockDelta?.delta) {
410
- const delta = event.contentBlockDelta.delta
411
- if ('text' in delta && delta.text) {
412
- yield {
413
- id: requestId,
414
- delta: { content: delta.text },
415
- }
416
- }
417
-
418
- if ('toolUse' in delta && delta.toolUse) {
419
- const idx = event.contentBlockDelta.contentBlockIndex ?? toolCallIndex
420
- const active = activeToolCalls.get(idx)
421
- if (active) {
422
- active.args += delta.toolUse.input ?? ''
423
- yield {
424
- id: requestId,
425
- delta: {
426
- toolCalls: [
427
- {
428
- index: idx,
429
- function: { arguments: delta.toolUse.input ?? '' },
430
- },
431
- ],
432
- },
433
- }
434
- }
435
- }
436
- }
437
-
438
- if ('contentBlockStart' in event && event.contentBlockStart?.start) {
439
- const start = event.contentBlockStart.start
440
- if ('toolUse' in start && start.toolUse) {
441
- const idx = event.contentBlockStart.contentBlockIndex ?? toolCallIndex
442
- activeToolCalls.set(idx, {
443
- id: start.toolUse.toolUseId ?? `tool-${Date.now()}`,
444
- name: start.toolUse.name ?? '',
445
- args: '',
446
- })
447
- yield {
448
- id: requestId,
449
- delta: {
450
- toolCalls: [
451
- {
452
- index: idx,
453
- id: start.toolUse.toolUseId,
454
- type: 'function',
455
- function: { name: start.toolUse.name ?? '' },
456
- },
457
- ],
458
- },
459
- }
460
- toolCallIndex = idx + 1
461
- }
462
- }
463
-
464
- if ('contentBlockStop' in event) {
465
- }
466
-
467
- if ('messageStop' in event && event.messageStop) {
468
- yield {
469
- id: requestId,
470
- delta: {},
471
- finishReason: mapStopReason(event.messageStop.stopReason),
472
- }
473
- }
474
-
475
- if ('metadata' in event && event.metadata?.usage) {
476
- const usage = parseUsage(event.metadata.usage as RawBedrockUsage)
477
- yield {
478
- id: requestId,
479
- delta: {},
480
- usage,
481
- }
482
- }
483
- } catch (parseErr) {
484
- logger.warn('Failed to process Bedrock stream event', {
485
- error: toErrorMessage(parseErr),
486
- })
487
- yield {
488
- id: requestId,
489
- delta: { content: undefined },
490
- finishReason: undefined,
491
- usage: undefined,
492
- error: `Stream parse error: ${toErrorMessage(parseErr)}`,
493
- }
494
- }
495
- }
496
- }
497
-
498
- async listModels(): Promise<ModelInfo[]> {
499
- return [
500
- {
501
- id: 'anthropic.claude-sonnet-4-20250514',
502
- name: 'Claude Sonnet 4 (Bedrock)',
503
- contextWindow: 200_000,
504
- maxOutputTokens: 64_000,
505
- inputPrice: 3.0,
506
- outputPrice: 15.0,
507
- supportsToolUse: true,
508
- supportsStreaming: true,
509
- },
510
- {
511
- id: 'anthropic.claude-haiku-4-20250514',
512
- name: 'Claude Haiku 4 (Bedrock)',
513
- contextWindow: 200_000,
514
- maxOutputTokens: 64_000,
515
- inputPrice: 0.8,
516
- outputPrice: 4.0,
517
- supportsToolUse: true,
518
- supportsStreaming: true,
519
- },
520
- {
521
- id: 'amazon.nova-pro-v1:0',
522
- name: 'Amazon Nova Pro',
523
- contextWindow: 300_000,
524
- maxOutputTokens: 5_000,
525
- inputPrice: 0.8,
526
- outputPrice: 3.2,
527
- supportsToolUse: true,
528
- supportsStreaming: true,
529
- },
530
- ]
531
- }
532
-
533
- async healthCheck(): Promise<boolean> {
534
- try {
535
- const command = new ConverseCommand({
536
- modelId: 'anthropic.claude-haiku-4-20250514',
537
- messages: [{ role: 'user', content: [{ text: 'hi' }] }],
538
- inferenceConfig: { maxTokens: 1 },
539
- })
540
- const response = await this.client.send(command, {
541
- requestTimeout: 5000,
542
- })
543
- return response.$metadata.httpStatusCode === 200
544
- } catch {
545
- return false
546
- }
547
- }
548
- }