@things-factory/ai-client-base 10.0.0-beta.64

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 (46) hide show
  1. package/config/config.development.js +72 -0
  2. package/config/config.production.js +24 -0
  3. package/dist-server/index.d.ts +19 -0
  4. package/dist-server/index.js +23 -0
  5. package/dist-server/index.js.map +1 -0
  6. package/dist-server/service/factory.d.ts +16 -0
  7. package/dist-server/service/factory.js +65 -0
  8. package/dist-server/service/factory.js.map +1 -0
  9. package/dist-server/service/index.d.ts +4 -0
  10. package/dist-server/service/index.js +8 -0
  11. package/dist-server/service/index.js.map +1 -0
  12. package/dist-server/service/providers/anthropic.d.ts +13 -0
  13. package/dist-server/service/providers/anthropic.js +201 -0
  14. package/dist-server/service/providers/anthropic.js.map +1 -0
  15. package/dist-server/service/providers/gemini.d.ts +14 -0
  16. package/dist-server/service/providers/gemini.js +292 -0
  17. package/dist-server/service/providers/gemini.js.map +1 -0
  18. package/dist-server/service/providers/index.d.ts +6 -0
  19. package/dist-server/service/providers/index.js +14 -0
  20. package/dist-server/service/providers/index.js.map +1 -0
  21. package/dist-server/service/providers/openai.d.ts +13 -0
  22. package/dist-server/service/providers/openai.js +232 -0
  23. package/dist-server/service/providers/openai.js.map +1 -0
  24. package/dist-server/service/types.d.ts +171 -0
  25. package/dist-server/service/types.js +8 -0
  26. package/dist-server/service/types.js.map +1 -0
  27. package/dist-server/service/utils/index.d.ts +1 -0
  28. package/dist-server/service/utils/index.js +5 -0
  29. package/dist-server/service/utils/index.js.map +1 -0
  30. package/dist-server/service/utils/parse-json.d.ts +20 -0
  31. package/dist-server/service/utils/parse-json.js +99 -0
  32. package/dist-server/service/utils/parse-json.js.map +1 -0
  33. package/dist-server/tsconfig.tsbuildinfo +1 -0
  34. package/package.json +31 -0
  35. package/server/index.ts +19 -0
  36. package/server/service/factory.ts +68 -0
  37. package/server/service/index.ts +4 -0
  38. package/server/service/providers/anthropic.ts +252 -0
  39. package/server/service/providers/gemini.ts +353 -0
  40. package/server/service/providers/index.ts +8 -0
  41. package/server/service/providers/openai.ts +284 -0
  42. package/server/service/types.ts +197 -0
  43. package/server/service/utils/index.ts +1 -0
  44. package/server/service/utils/parse-json.ts +100 -0
  45. package/things-factory.config.js +1 -0
  46. package/tsconfig.json +9 -0
@@ -0,0 +1,72 @@
1
+ module.exports = {
2
+ /*
3
+ AI Client Configuration
4
+
5
+ Provider-agnostic configuration for the default AI client used by board-ai
6
+ and any other module that calls `getDefaultAIClient()`.
7
+
8
+ To activate, override this in your application's config (config.development.js
9
+ or config.production.js) with your provider/key.
10
+
11
+ Currently supported providers (registered via @things-factory/ai-client-base):
12
+ - 'anthropic' — Anthropic Claude (default model: claude-sonnet-4-6)
13
+ - 'gemini' — Google Gemini (default model: gemini-3-pro)
14
+ - 'openai' — OpenAI GPT (default model: gpt-4o)
15
+
16
+ Example for Anthropic Claude:
17
+
18
+ aiClient: {
19
+ provider: 'anthropic',
20
+ model: 'claude-sonnet-4-6',
21
+ apiKey: process.env.ANTHROPIC_API_KEY
22
+ }
23
+
24
+ Example for Google Gemini:
25
+
26
+ aiClient: {
27
+ provider: 'gemini',
28
+ model: 'gemini-3-pro',
29
+ apiKey: process.env.GOOGLE_API_KEY
30
+ }
31
+
32
+ Example for on-prem / proxy / Bedrock-style endpoint:
33
+
34
+ aiClient: {
35
+ provider: 'anthropic',
36
+ model: 'claude-sonnet-4-6',
37
+ apiKey: 'internal-key',
38
+ baseURL: 'https://internal-llm-gateway.example.com'
39
+ }
40
+
41
+ Fields:
42
+ - provider : Registered adapter name. Required.
43
+ - model : Model ID. Optional; falls back to adapter default.
44
+ - apiKey : API key. If omitted, SDK-level environment fallback applies
45
+ (Anthropic SDK: ANTHROPIC_API_KEY; Gemini adapter: GOOGLE_API_KEY,
46
+ GEMINI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY).
47
+ - baseURL : Optional. On-prem / proxy / compatible endpoint.
48
+
49
+ Example for OpenAI:
50
+
51
+ aiClient: {
52
+ provider: 'openai',
53
+ model: 'gpt-4o',
54
+ apiKey: process.env.OPENAI_API_KEY,
55
+ organization: process.env.OPENAI_ORGANIZATION // optional
56
+ }
57
+
58
+ Note on the legacy `openai` config key:
59
+ @things-factory/codelingua uses a separate `openai: { organization, apiKey }` key.
60
+ The two keys are intentionally independent — codelingua keeps using `openai`,
61
+ ai-client-base only reads `aiClient`. If you want OpenAI here too, set `aiClient`
62
+ explicitly with provider:'openai' (the values may duplicate codelingua's,
63
+ that's fine — they live in different modules).
64
+
65
+ If `aiClient` is absent, no default client is created — chat() calls will fail
66
+ with a clear message. Modules can still be used with an explicitly-passed AIClient
67
+ via setDefaultAIClient(...).
68
+ */
69
+ // aiClient: {
70
+ // provider: 'anthropic'
71
+ // }
72
+ }
@@ -0,0 +1,24 @@
1
+ module.exports = {
2
+ /*
3
+ AI Client Configuration — Production
4
+
5
+ See config.development.js for the full reference and provider examples.
6
+
7
+ Override this in your application's production config with the actual
8
+ provider and credentials. Never commit real API keys to the source tree.
9
+
10
+ Recommended pattern — read from environment in your application config:
11
+
12
+ aiClient: {
13
+ provider: process.env.AI_PROVIDER || 'anthropic',
14
+ model: process.env.AI_MODEL,
15
+ apiKey: process.env.AI_API_KEY
16
+ || process.env.ANTHROPIC_API_KEY
17
+ || process.env.GOOGLE_API_KEY,
18
+ baseURL: process.env.AI_BASE_URL
19
+ }
20
+ */
21
+ // aiClient: {
22
+ // provider: 'anthropic'
23
+ // }
24
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @things-factory/ai-client-base — Provider-agnostic AI client abstraction.
3
+ *
4
+ * 사용 예:
5
+ * ```ts
6
+ * import { createAIClient } from '@things-factory/ai-client-base'
7
+ *
8
+ * const client = createAIClient({
9
+ * provider: 'anthropic',
10
+ * model: 'claude-sonnet-4-6',
11
+ * apiKey: process.env.ANTHROPIC_API_KEY
12
+ * })
13
+ *
14
+ * const text = await client.chat([{ role: 'user', content: 'Hello' }])
15
+ * const json = await client.generateJSON<MyShape>('Return {"x": 1, "y": 2} as JSON')
16
+ * const desc = await client.vision(imageBuffer, 'What is in this image?')
17
+ * ```
18
+ */
19
+ export * from './service/index.js';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ /**
5
+ * @things-factory/ai-client-base — Provider-agnostic AI client abstraction.
6
+ *
7
+ * 사용 예:
8
+ * ```ts
9
+ * import { createAIClient } from '@things-factory/ai-client-base'
10
+ *
11
+ * const client = createAIClient({
12
+ * provider: 'anthropic',
13
+ * model: 'claude-sonnet-4-6',
14
+ * apiKey: process.env.ANTHROPIC_API_KEY
15
+ * })
16
+ *
17
+ * const text = await client.chat([{ role: 'user', content: 'Hello' }])
18
+ * const json = await client.generateJSON<MyShape>('Return {"x": 1, "y": 2} as JSON')
19
+ * const desc = await client.vision(imageBuffer, 'What is in this image?')
20
+ * ```
21
+ */
22
+ tslib_1.__exportStar(require("./service/index.js"), exports);
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,6DAAkC","sourcesContent":["/**\n * @things-factory/ai-client-base — Provider-agnostic AI client abstraction.\n *\n * 사용 예:\n * ```ts\n * import { createAIClient } from '@things-factory/ai-client-base'\n *\n * const client = createAIClient({\n * provider: 'anthropic',\n * model: 'claude-sonnet-4-6',\n * apiKey: process.env.ANTHROPIC_API_KEY\n * })\n *\n * const text = await client.chat([{ role: 'user', content: 'Hello' }])\n * const json = await client.generateJSON<MyShape>('Return {\"x\": 1, \"y\": 2} as JSON')\n * const desc = await client.vision(imageBuffer, 'What is in this image?')\n * ```\n */\nexport * from './service/index.js'\n"]}
@@ -0,0 +1,16 @@
1
+ import type { AIClient, AIClientConfig, AIProviderFactory } from './types.js';
2
+ export declare function registerAIProvider(name: string, factory: AIProviderFactory): void;
3
+ export declare function listRegisteredProviders(): string[];
4
+ export declare function createAIClient(config: AIClientConfig): AIClient;
5
+ /**
6
+ * 기본 client.
7
+ * 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것
8
+ * 2) env config 의 `aiClient` 키가 있으면 거기서 생성
9
+ * 3) 없으면 undefined (호출자는 nullable 처리)
10
+ *
11
+ * 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.
12
+ *
13
+ * 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.
14
+ */
15
+ export declare function getDefaultAIClient(): AIClient | undefined;
16
+ export declare function setDefaultAIClient(client: AIClient | undefined): void;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAIProvider = registerAIProvider;
4
+ exports.listRegisteredProviders = listRegisteredProviders;
5
+ exports.createAIClient = createAIClient;
6
+ exports.getDefaultAIClient = getDefaultAIClient;
7
+ exports.setDefaultAIClient = setDefaultAIClient;
8
+ /**
9
+ * AI Provider 등록 + Client 생성.
10
+ *
11
+ * - 어댑터 패키지(또는 동일 패키지의 providers/) 가 모듈 로드 시 `registerAIProvider` 호출.
12
+ * - 호출자는 `createAIClient({ provider: 'anthropic', ... })` 로 인스턴스 획득.
13
+ * - things-factory env config 의 `aiClient` 키가 있으면 `getDefaultAIClient()` 가 자동 생성.
14
+ */
15
+ const env_1 = require("@things-factory/env");
16
+ const REGISTERED_PROVIDERS = new Map();
17
+ function registerAIProvider(name, factory) {
18
+ REGISTERED_PROVIDERS.set(name, factory);
19
+ }
20
+ function listRegisteredProviders() {
21
+ return Array.from(REGISTERED_PROVIDERS.keys());
22
+ }
23
+ function createAIClient(config) {
24
+ const factory = REGISTERED_PROVIDERS.get(config.provider);
25
+ if (!factory) {
26
+ throw new Error(`Unknown AI provider "${config.provider}". Registered: ${listRegisteredProviders().join(', ') || '(none)'}`);
27
+ }
28
+ return factory(config);
29
+ }
30
+ let _defaultClient;
31
+ /**
32
+ * 기본 client.
33
+ * 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것
34
+ * 2) env config 의 `aiClient` 키가 있으면 거기서 생성
35
+ * 3) 없으면 undefined (호출자는 nullable 처리)
36
+ *
37
+ * 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.
38
+ *
39
+ * 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.
40
+ */
41
+ function getDefaultAIClient() {
42
+ if (_defaultClient)
43
+ return _defaultClient;
44
+ const cfg = env_1.config.get('aiClient', null);
45
+ if (!cfg)
46
+ return undefined;
47
+ if (!cfg.provider) {
48
+ console.warn('[ai-client-base] config.aiClient is set but missing required "provider" field. ' +
49
+ 'See @things-factory/ai-client-base/config/config.development.js for examples.');
50
+ return undefined;
51
+ }
52
+ try {
53
+ _defaultClient = createAIClient(cfg);
54
+ }
55
+ catch (e) {
56
+ console.warn(`[ai-client-base] Failed to create default AI client: ${e.message}. ` +
57
+ `Registered providers: ${listRegisteredProviders().join(', ') || '(none)'}`);
58
+ return undefined;
59
+ }
60
+ return _defaultClient;
61
+ }
62
+ function setDefaultAIClient(client) {
63
+ _defaultClient = client;
64
+ }
65
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../server/service/factory.ts"],"names":[],"mappings":";;AAYA,gDAEC;AAED,0DAEC;AAED,wCAQC;AAcD,gDAqBC;AAED,gDAEC;AAnED;;;;;;GAMG;AACH,6CAAyD;AAGzD,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA6B,CAAA;AAEjE,SAAgB,kBAAkB,CAAC,IAAY,EAAE,OAA0B;IACzE,oBAAoB,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;AACzC,CAAC;AAED,SAAgB,uBAAuB;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAA;AAChD,CAAC;AAED,SAAgB,cAAc,CAAC,MAAsB;IACnD,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IACzD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,CAAC,QAAQ,kBAAkB,uBAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAC5G,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAA;AACxB,CAAC;AAED,IAAI,cAAoC,CAAA;AAExC;;;;;;;;;GASG;AACH,SAAgB,kBAAkB;IAChC,IAAI,cAAc;QAAE,OAAO,cAAc,CAAA;IACzC,MAAM,GAAG,GAAG,YAAS,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAA0B,CAAA;IACpE,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,IAAI,CACV,iFAAiF;YAC/E,+EAA+E,CAClF,CAAA;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,CAAC;QACH,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,CAAA;IACtC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,IAAI,CACV,wDAAyD,CAAW,CAAC,OAAO,IAAI;YAC9E,yBAAyB,uBAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAC9E,CAAA;QACD,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC;AAED,SAAgB,kBAAkB,CAAC,MAA4B;IAC7D,cAAc,GAAG,MAAM,CAAA;AACzB,CAAC","sourcesContent":["/**\n * AI Provider 등록 + Client 생성.\n *\n * - 어댑터 패키지(또는 동일 패키지의 providers/) 가 모듈 로드 시 `registerAIProvider` 호출.\n * - 호출자는 `createAIClient({ provider: 'anthropic', ... })` 로 인스턴스 획득.\n * - things-factory env config 의 `aiClient` 키가 있으면 `getDefaultAIClient()` 가 자동 생성.\n */\nimport { config as envConfig } from '@things-factory/env'\nimport type { AIClient, AIClientConfig, AIProviderFactory } from './types.js'\n\nconst REGISTERED_PROVIDERS = new Map<string, AIProviderFactory>()\n\nexport function registerAIProvider(name: string, factory: AIProviderFactory): void {\n REGISTERED_PROVIDERS.set(name, factory)\n}\n\nexport function listRegisteredProviders(): string[] {\n return Array.from(REGISTERED_PROVIDERS.keys())\n}\n\nexport function createAIClient(config: AIClientConfig): AIClient {\n const factory = REGISTERED_PROVIDERS.get(config.provider)\n if (!factory) {\n throw new Error(\n `Unknown AI provider \"${config.provider}\". Registered: ${listRegisteredProviders().join(', ') || '(none)'}`\n )\n }\n return factory(config)\n}\n\nlet _defaultClient: AIClient | undefined\n\n/**\n * 기본 client.\n * 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것\n * 2) env config 의 `aiClient` 키가 있으면 거기서 생성\n * 3) 없으면 undefined (호출자는 nullable 처리)\n *\n * 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.\n *\n * 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.\n */\nexport function getDefaultAIClient(): AIClient | undefined {\n if (_defaultClient) return _defaultClient\n const cfg = envConfig.get('aiClient', null) as AIClientConfig | null\n if (!cfg) return undefined\n if (!cfg.provider) {\n console.warn(\n '[ai-client-base] config.aiClient is set but missing required \"provider\" field. ' +\n 'See @things-factory/ai-client-base/config/config.development.js for examples.'\n )\n return undefined\n }\n try {\n _defaultClient = createAIClient(cfg)\n } catch (e) {\n console.warn(\n `[ai-client-base] Failed to create default AI client: ${(e as Error).message}. ` +\n `Registered providers: ${listRegisteredProviders().join(', ') || '(none)'}`\n )\n return undefined\n }\n return _defaultClient\n}\n\nexport function setDefaultAIClient(client: AIClient | undefined): void {\n _defaultClient = client\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './factory.js';
3
+ export * from './providers/index.js';
4
+ export * from './utils/index.js';
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./types.js"), exports);
5
+ tslib_1.__exportStar(require("./factory.js"), exports);
6
+ tslib_1.__exportStar(require("./providers/index.js"), exports);
7
+ tslib_1.__exportStar(require("./utils/index.js"), exports);
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;AAAA,qDAA0B;AAC1B,uDAA4B;AAC5B,+DAAoC;AACpC,2DAAgC","sourcesContent":["export * from './types.js'\nexport * from './factory.js'\nexport * from './providers/index.js'\nexport * from './utils/index.js'\n"]}
@@ -0,0 +1,13 @@
1
+ import type { AIChatOptions, AIClient, AIClientConfig, AIJSONOptions, AIMessage, AITool, AIToolChatOptions, AIToolChatResult, AIVisionOptions } from '../types.js';
2
+ export declare class AnthropicAIClient implements AIClient {
3
+ readonly id: string;
4
+ private sdk;
5
+ private defaultModel;
6
+ constructor(config: AIClientConfig);
7
+ chat(messages: AIMessage[], options?: AIChatOptions): Promise<string>;
8
+ generateJSON<T = unknown>(prompt: string | AIMessage[], options?: AIJSONOptions): Promise<T>;
9
+ vision(image: Buffer | Uint8Array | {
10
+ url: string;
11
+ }, prompt: string, options?: AIVisionOptions): Promise<string>;
12
+ chatWithTools(messages: AIMessage[], tools: AITool[], options?: AIToolChatOptions): Promise<AIToolChatResult>;
13
+ }
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AnthropicAIClient = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /**
6
+ * Anthropic (Claude) AI Client adapter.
7
+ *
8
+ * 기본 모델: claude-sonnet-4-6 (구성에서 override 가능).
9
+ * SDK: @anthropic-ai/sdk
10
+ */
11
+ const sdk_1 = tslib_1.__importDefault(require("@anthropic-ai/sdk"));
12
+ const factory_js_1 = require("../factory.js");
13
+ const parse_json_js_1 = require("../utils/parse-json.js");
14
+ const DEFAULT_MODEL = 'claude-sonnet-4-6';
15
+ const DEFAULT_MAX_TOKENS = 4096;
16
+ class AnthropicAIClient {
17
+ constructor(config) {
18
+ this.sdk = new sdk_1.default({
19
+ apiKey: config.apiKey,
20
+ ...(config.baseURL && { baseURL: config.baseURL })
21
+ });
22
+ this.defaultModel = config.model ?? DEFAULT_MODEL;
23
+ this.id = `anthropic:${this.defaultModel}`;
24
+ }
25
+ async chat(messages, options = {}) {
26
+ const systemFromMessages = messages
27
+ .filter(m => m.role === 'system')
28
+ .map(extractTextContent)
29
+ .filter(Boolean)
30
+ .join('\n\n');
31
+ const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\n\n');
32
+ const conversation = messages
33
+ .filter(m => m.role !== 'system')
34
+ .map(toAnthropicMessage);
35
+ const response = await this.sdk.messages.create({
36
+ model: options.model ?? this.defaultModel,
37
+ max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
38
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
39
+ ...(system && { system }),
40
+ ...(options.stopSequences && { stop_sequences: options.stopSequences }),
41
+ messages: conversation
42
+ });
43
+ return extractResponseText(response);
44
+ }
45
+ async generateJSON(prompt, options = {}) {
46
+ const messages = typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt;
47
+ const schemaHint = options.schema
48
+ ? `\n\nResponse MUST conform to this JSON schema:\n${JSON.stringify(options.schema)}`
49
+ : '';
50
+ const enforcedSystem = (options.systemPrompt ? options.systemPrompt + '\n\n' : '') +
51
+ 'Return only valid JSON. No markdown code fences, no commentary.' +
52
+ schemaHint;
53
+ const text = await this.chat(messages, { ...options, systemPrompt: enforcedSystem });
54
+ return (0, parse_json_js_1.parseJSONResponse)(text);
55
+ }
56
+ async vision(image, prompt, options = {}) {
57
+ const imagePart = typeof image === 'object' && 'url' in image
58
+ ? { type: 'image', source: { kind: 'url', url: image.url } }
59
+ : {
60
+ type: 'image',
61
+ source: { kind: 'base64', data: image, mediaType: 'image/png' }
62
+ };
63
+ return this.chat([{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }], options);
64
+ }
65
+ async chatWithTools(messages, tools, options = {}) {
66
+ const systemFromMessages = messages
67
+ .filter(m => m.role === 'system')
68
+ .map(extractTextContent)
69
+ .filter(Boolean)
70
+ .join('\n\n');
71
+ const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\n\n');
72
+ const conversation = messages.filter(m => m.role !== 'system').map(toAnthropicMessage);
73
+ const sdkTools = tools.map(t => ({
74
+ name: t.name,
75
+ description: t.description,
76
+ input_schema: t.parameters
77
+ }));
78
+ const toolChoice = buildAnthropicToolChoice(options);
79
+ const response = await this.sdk.messages.create({
80
+ model: options.model ?? this.defaultModel,
81
+ max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
82
+ ...(options.temperature !== undefined && { temperature: options.temperature }),
83
+ ...(system && { system }),
84
+ ...(options.stopSequences && { stop_sequences: options.stopSequences }),
85
+ messages: conversation,
86
+ tools: sdkTools,
87
+ ...(toolChoice && { tool_choice: toolChoice })
88
+ });
89
+ const text = extractResponseText(response) || undefined;
90
+ const toolCalls = Array.isArray(response.content)
91
+ ? response.content
92
+ .filter((b) => b && b.type === 'tool_use')
93
+ .map((b) => ({
94
+ id: typeof b.id === 'string' && b.id ? b.id : `call_${Math.random().toString(36).slice(2, 12)}`,
95
+ name: b.name,
96
+ arguments: (b.input && typeof b.input === 'object' ? b.input : {})
97
+ }))
98
+ : [];
99
+ return {
100
+ text,
101
+ toolCalls,
102
+ stopReason: normalizeAnthropicStopReason(response.stop_reason)
103
+ };
104
+ }
105
+ }
106
+ exports.AnthropicAIClient = AnthropicAIClient;
107
+ // ── 변환 유틸 ─────────────────────────────────────────────────────────
108
+ function extractTextContent(msg) {
109
+ if (typeof msg.content === 'string')
110
+ return msg.content;
111
+ return msg.content
112
+ .filter(p => p.type === 'text')
113
+ .map(p => p.text)
114
+ .join('\n');
115
+ }
116
+ function toAnthropicMessage(msg) {
117
+ if (typeof msg.content === 'string') {
118
+ return { role: msg.role, content: msg.content };
119
+ }
120
+ return {
121
+ role: msg.role,
122
+ content: msg.content.map(toAnthropicContentBlock)
123
+ };
124
+ }
125
+ function toAnthropicContentBlock(part) {
126
+ if (part.type === 'text') {
127
+ return { type: 'text', text: part.text };
128
+ }
129
+ if (part.type === 'tool_use') {
130
+ return { type: 'tool_use', id: part.id, name: part.name, input: part.arguments };
131
+ }
132
+ if (part.type === 'tool_result') {
133
+ return {
134
+ type: 'tool_result',
135
+ tool_use_id: part.toolUseId,
136
+ content: part.content,
137
+ ...(part.isError && { is_error: true })
138
+ };
139
+ }
140
+ // image
141
+ if (part.source.kind === 'url') {
142
+ return { type: 'image', source: { type: 'url', url: part.source.url } };
143
+ }
144
+ const data = Buffer.isBuffer(part.source.data)
145
+ ? part.source.data.toString('base64')
146
+ : Buffer.from(part.source.data).toString('base64');
147
+ return {
148
+ type: 'image',
149
+ source: { type: 'base64', media_type: part.source.mediaType, data }
150
+ };
151
+ }
152
+ function extractResponseText(response) {
153
+ if (!response || !Array.isArray(response.content))
154
+ return '';
155
+ return response.content
156
+ .filter((b) => b && b.type === 'text')
157
+ .map((b) => b.text || '')
158
+ .join('');
159
+ }
160
+ function buildAnthropicToolChoice(options) {
161
+ const choice = options.toolChoice ?? 'auto';
162
+ const disableParallel = options.allowParallelToolCalls === false;
163
+ if (choice === 'none') {
164
+ return { type: 'none' };
165
+ }
166
+ if (choice === 'required') {
167
+ if (options.forceTool) {
168
+ return {
169
+ type: 'tool',
170
+ name: options.forceTool,
171
+ ...(disableParallel && { disable_parallel_tool_use: true })
172
+ };
173
+ }
174
+ return {
175
+ type: 'any',
176
+ ...(disableParallel && { disable_parallel_tool_use: true })
177
+ };
178
+ }
179
+ // 'auto'
180
+ return {
181
+ type: 'auto',
182
+ ...(disableParallel && { disable_parallel_tool_use: true })
183
+ };
184
+ }
185
+ function normalizeAnthropicStopReason(raw) {
186
+ switch (raw) {
187
+ case 'tool_use':
188
+ return 'tool_use';
189
+ case 'end_turn':
190
+ return 'end_turn';
191
+ case 'max_tokens':
192
+ return 'max_tokens';
193
+ case 'stop_sequence':
194
+ return 'stop_sequence';
195
+ default:
196
+ return raw ?? 'end_turn';
197
+ }
198
+ }
199
+ // Auto-register at module load
200
+ (0, factory_js_1.registerAIProvider)('anthropic', config => new AnthropicAIClient(config));
201
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../server/service/providers/anthropic.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Anthropic (Claude) AI Client adapter.\n *\n * 기본 모델: claude-sonnet-4-6 (구성에서 override 가능).\n * SDK: @anthropic-ai/sdk\n */\nimport Anthropic from '@anthropic-ai/sdk'\nimport type {\n AIChatOptions,\n AIClient,\n AIClientConfig,\n AIContentPart,\n AIJSONOptions,\n AIMessage,\n AITool,\n AIToolCall,\n AIToolChatOptions,\n AIToolChatResult,\n AIVisionOptions\n} from '../types.js'\nimport { registerAIProvider } from '../factory.js'\nimport { parseJSONResponse } from '../utils/parse-json.js'\n\nconst DEFAULT_MODEL = 'claude-sonnet-4-6'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class AnthropicAIClient implements AIClient {\n readonly id: string\n private sdk: Anthropic\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new Anthropic({\n apiKey: config.apiKey,\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `anthropic:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n\n const conversation = messages\n .filter(m => m.role !== 'system')\n .map(toAnthropicMessage)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation\n } as any)\n\n return extractResponseText(response)\n }\n\n async generateJSON<T = unknown>(\n prompt: string | AIMessage[],\n options: AIJSONOptions = {}\n ): Promise<T> {\n const messages: AIMessage[] =\n typeof prompt === 'string' ? [{ role: 'user', content: prompt }] : prompt\n\n const schemaHint = options.schema\n ? `\\n\\nResponse MUST conform to this JSON schema:\\n${JSON.stringify(options.schema)}`\n : ''\n const enforcedSystem =\n (options.systemPrompt ? options.systemPrompt + '\\n\\n' : '') +\n 'Return only valid JSON. No markdown code fences, no commentary.' +\n schemaHint\n\n const text = await this.chat(messages, { ...options, systemPrompt: enforcedSystem })\n return parseJSONResponse<T>(text)\n }\n\n async vision(\n image: Buffer | Uint8Array | { url: string },\n prompt: string,\n options: AIVisionOptions = {}\n ): Promise<string> {\n const imagePart: AIContentPart =\n typeof image === 'object' && 'url' in image\n ? { type: 'image', source: { kind: 'url', url: image.url } }\n : {\n type: 'image',\n source: { kind: 'base64', data: image as Buffer | Uint8Array, mediaType: 'image/png' }\n }\n\n return this.chat(\n [{ role: 'user', content: [imagePart, { type: 'text', text: prompt }] }],\n options\n )\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const system = [options.systemPrompt, systemFromMessages].filter(Boolean).join('\\n\\n')\n const conversation = messages.filter(m => m.role !== 'system').map(toAnthropicMessage)\n\n const sdkTools = tools.map(t => ({\n name: t.name,\n description: t.description,\n input_schema: t.parameters as any\n }))\n\n const toolChoice = buildAnthropicToolChoice(options)\n\n const response = await this.sdk.messages.create({\n model: options.model ?? this.defaultModel,\n max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(system && { system }),\n ...(options.stopSequences && { stop_sequences: options.stopSequences }),\n messages: conversation,\n tools: sdkTools,\n ...(toolChoice && { tool_choice: toolChoice })\n } as any)\n\n const text = extractResponseText(response) || undefined\n const toolCalls: AIToolCall[] = Array.isArray(response.content)\n ? response.content\n .filter((b: any) => b && b.type === 'tool_use')\n .map((b: any) => ({\n id: typeof b.id === 'string' && b.id ? b.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: b.name,\n arguments: (b.input && typeof b.input === 'object' ? b.input : {}) as Record<string, any>\n }))\n : []\n\n return {\n text,\n toolCalls,\n stopReason: normalizeAnthropicStopReason(response.stop_reason)\n }\n }\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction extractTextContent(msg: AIMessage): string {\n if (typeof msg.content === 'string') return msg.content\n return msg.content\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('\\n')\n}\n\nfunction toAnthropicMessage(msg: AIMessage): any {\n if (typeof msg.content === 'string') {\n return { role: msg.role, content: msg.content }\n }\n return {\n role: msg.role,\n content: msg.content.map(toAnthropicContentBlock)\n }\n}\n\nfunction toAnthropicContentBlock(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use') {\n return { type: 'tool_use', id: part.id, name: part.name, input: part.arguments }\n }\n if (part.type === 'tool_result') {\n return {\n type: 'tool_result',\n tool_use_id: part.toolUseId,\n content: part.content,\n ...(part.isError && { is_error: true })\n }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image', source: { type: 'url', url: part.source.url } }\n }\n const data = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image',\n source: { type: 'base64', media_type: part.source.mediaType, data }\n }\n}\n\nfunction extractResponseText(response: any): string {\n if (!response || !Array.isArray(response.content)) return ''\n return response.content\n .filter((b: any) => b && b.type === 'text')\n .map((b: any) => b.text || '')\n .join('')\n}\n\nfunction buildAnthropicToolChoice(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n const disableParallel = options.allowParallelToolCalls === false\n if (choice === 'none') {\n return { type: 'none' }\n }\n if (choice === 'required') {\n if (options.forceTool) {\n return {\n type: 'tool',\n name: options.forceTool,\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n return {\n type: 'any',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n }\n // 'auto'\n return {\n type: 'auto',\n ...(disableParallel && { disable_parallel_tool_use: true })\n }\n}\n\nfunction normalizeAnthropicStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_use':\n return 'tool_use'\n case 'end_turn':\n return 'end_turn'\n case 'max_tokens':\n return 'max_tokens'\n case 'stop_sequence':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('anthropic', config => new AnthropicAIClient(config))\n"]}
@@ -0,0 +1,14 @@
1
+ import type { AIChatOptions, AIClient, AIClientConfig, AIJSONOptions, AIMessage, AITool, AIToolChatOptions, AIToolChatResult, AIVisionOptions } from '../types.js';
2
+ export declare class GeminiAIClient implements AIClient {
3
+ readonly id: string;
4
+ private sdk;
5
+ private defaultModel;
6
+ constructor(config: AIClientConfig);
7
+ chat(messages: AIMessage[], options?: AIChatOptions): Promise<string>;
8
+ generateJSON<T = unknown>(prompt: string | AIMessage[], options?: AIJSONOptions): Promise<T>;
9
+ vision(image: Buffer | Uint8Array | {
10
+ url: string;
11
+ }, prompt: string, options?: AIVisionOptions): Promise<string>;
12
+ private buildModel;
13
+ chatWithTools(messages: AIMessage[], tools: AITool[], options?: AIToolChatOptions): Promise<AIToolChatResult>;
14
+ }