@things-factory/ai-client-base 10.1.149 → 10.1.157

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 (76) hide show
  1. package/config/config.development.js +2 -68
  2. package/config/config.production.js +2 -20
  3. package/dist-server/service/agent/agent-directory.d.ts +71 -0
  4. package/dist-server/service/agent/agent-directory.js +129 -0
  5. package/dist-server/service/agent/agent-directory.js.map +1 -0
  6. package/dist-server/service/agent/agent-settings-save.d.ts +38 -0
  7. package/dist-server/service/agent/agent-settings-save.js +153 -0
  8. package/dist-server/service/agent/agent-settings-save.js.map +1 -0
  9. package/dist-server/service/agent/agent-spec.d.ts +43 -0
  10. package/dist-server/service/agent/agent-spec.js +83 -0
  11. package/dist-server/service/agent/agent-spec.js.map +1 -0
  12. package/dist-server/service/agent/ai-client-for.d.ts +53 -0
  13. package/dist-server/service/agent/ai-client-for.js +166 -0
  14. package/dist-server/service/agent/ai-client-for.js.map +1 -0
  15. package/dist-server/service/agent/ai-usage-summary.d.ts +37 -0
  16. package/dist-server/service/agent/ai-usage-summary.js +108 -0
  17. package/dist-server/service/agent/ai-usage-summary.js.map +1 -0
  18. package/dist-server/service/agent/ai-usage.d.ts +35 -0
  19. package/dist-server/service/agent/ai-usage.js +118 -0
  20. package/dist-server/service/agent/ai-usage.js.map +1 -0
  21. package/dist-server/service/agent/index.d.ts +7 -0
  22. package/dist-server/service/agent/index.js +13 -0
  23. package/dist-server/service/agent/index.js.map +1 -0
  24. package/dist-server/service/agent/provider-models.d.ts +12 -0
  25. package/dist-server/service/agent/provider-models.js +37 -0
  26. package/dist-server/service/agent/provider-models.js.map +1 -0
  27. package/dist-server/service/capability.d.ts +21 -10
  28. package/dist-server/service/capability.js +20 -49
  29. package/dist-server/service/capability.js.map +1 -1
  30. package/dist-server/service/factory.d.ts +7 -12
  31. package/dist-server/service/factory.js +0 -45
  32. package/dist-server/service/factory.js.map +1 -1
  33. package/dist-server/service/index.d.ts +3 -0
  34. package/dist-server/service/index.js +6 -1
  35. package/dist-server/service/index.js.map +1 -1
  36. package/dist-server/service/providers/anthropic.d.ts +2 -0
  37. package/dist-server/service/providers/anthropic.js +16 -0
  38. package/dist-server/service/providers/anthropic.js.map +1 -1
  39. package/dist-server/service/providers/gemini.d.ts +13 -0
  40. package/dist-server/service/providers/gemini.js +52 -12
  41. package/dist-server/service/providers/gemini.js.map +1 -1
  42. package/dist-server/service/providers/openai.d.ts +6 -0
  43. package/dist-server/service/providers/openai.js +34 -8
  44. package/dist-server/service/providers/openai.js.map +1 -1
  45. package/dist-server/service/types.d.ts +16 -2
  46. package/dist-server/service/types.js.map +1 -1
  47. package/dist-server/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +5 -2
  49. package/server/service/agent/agent-directory.test.ts +206 -0
  50. package/server/service/agent/agent-directory.ts +198 -0
  51. package/server/service/agent/agent-settings-save.test.ts +151 -0
  52. package/server/service/agent/agent-settings-save.ts +205 -0
  53. package/server/service/agent/agent-spec.ts +103 -0
  54. package/server/service/agent/ai-client-for.test.ts +141 -0
  55. package/server/service/agent/ai-client-for.ts +201 -0
  56. package/server/service/agent/ai-usage-summary.test.ts +65 -0
  57. package/server/service/agent/ai-usage-summary.ts +146 -0
  58. package/server/service/agent/ai-usage.ts +97 -0
  59. package/server/service/agent/index.ts +7 -0
  60. package/server/service/agent/one-door.test.ts +57 -0
  61. package/server/service/agent/provider-models.ts +43 -0
  62. package/server/service/capability.test.ts +26 -55
  63. package/server/service/capability.ts +25 -42
  64. package/server/service/factory.ts +4 -44
  65. package/server/service/index.ts +6 -1
  66. package/server/service/providers/anthropic.ts +17 -0
  67. package/server/service/providers/gemini.ts +54 -12
  68. package/server/service/providers/openai-models.test.ts +30 -0
  69. package/server/service/providers/openai.ts +34 -8
  70. package/server/service/types.ts +18 -2
  71. package/translations/en.json +13 -0
  72. package/translations/es.json +13 -0
  73. package/translations/ja.json +13 -0
  74. package/translations/ko.json +13 -0
  75. package/translations/ms.json +13 -0
  76. package/translations/zh.json +13 -0
@@ -1,11 +1,10 @@
1
1
  /**
2
- * AI Provider 등록 + Client 생성.
2
+ * Provider registration and client construction.
3
3
  *
4
- * - 어댑터 패키지(또는 동일 패키지의 providers/) 가 모듈 로드 시 `registerAIProvider` 호출.
5
- * - 호출자는 `createAIClient({ provider: 'anthropic', ... })` 로 인스턴스 획득.
6
- * - things-factory env config 의 `aiClient` 키가 있으면 `getDefaultAIClient()` 가 자동 생성.
4
+ * Each adapter registers itself when its module loads. `createAIClient` builds a client from a provider, model and
5
+ * key; it is how this package's own door (aiClientFor) and the model list reach a provider. Code outside this package
6
+ * gets a client from aiClientFor, for a domain - there is no deployment-wide client.
7
7
  */
8
- import { config as envConfig } from '@things-factory/env'
9
8
  import type { AIClient, AIClientConfig, AIProviderFactory } from './types.js'
10
9
 
11
10
  const REGISTERED_PROVIDERS = new Map<string, AIProviderFactory>()
@@ -27,42 +26,3 @@ export function createAIClient(config: AIClientConfig): AIClient {
27
26
  }
28
27
  return factory(config)
29
28
  }
30
-
31
- let _defaultClient: AIClient | undefined
32
-
33
- /**
34
- * 기본 client.
35
- * 1) `setDefaultAIClient()` 로 명시 설정된 게 있으면 그것
36
- * 2) env config 의 `aiClient` 키가 있으면 거기서 생성
37
- * 3) 없으면 undefined (호출자는 nullable 처리)
38
- *
39
- * 다른 모듈 (예: codelingua) 의 `openai` 키와는 의도적으로 분리. 두 키는 서로 모르고 동작.
40
- *
41
- * 잘못된 config (provider 누락, 등록되지 않은 provider) 는 console.warn 후 undefined.
42
- */
43
- export function getDefaultAIClient(): AIClient | undefined {
44
- if (_defaultClient) return _defaultClient
45
- const cfg = envConfig.get('aiClient', null) as AIClientConfig | null
46
- if (!cfg) return undefined
47
- if (!cfg.provider) {
48
- console.warn(
49
- '[ai-client-base] config.aiClient is set but missing required "provider" field. ' +
50
- 'See @things-factory/ai-client-base/config/config.development.js for examples.'
51
- )
52
- return undefined
53
- }
54
- try {
55
- _defaultClient = createAIClient(cfg)
56
- } catch (e) {
57
- console.warn(
58
- `[ai-client-base] Failed to create default AI client: ${(e as Error).message}. ` +
59
- `Registered providers: ${listRegisteredProviders().join(', ') || '(none)'}`
60
- )
61
- return undefined
62
- }
63
- return _defaultClient
64
- }
65
-
66
- export function setDefaultAIClient(client: AIClient | undefined): void {
67
- _defaultClient = client
68
- }
@@ -7,6 +7,11 @@ export * from './agentic-loop/index.js'
7
7
  export * from './assistant/index.js'
8
8
  export * from './analysis-contract.js'
9
9
  export * from './decision/index.js'
10
- /* 「이 배포에 AI 가 있나」 — 시도해서 알아내지 않고 먼저 읽는다(표준 A7-a). */
10
+ /* 「이 사업장에 AI 가 있나」 — 시도해서 알아내지 않고 먼저 읽는다(표준 A7-a). */
11
11
  export * from './capability.js'
12
+ /* AI agents per domain - the one door to a model, and its usage (design/ai/agent-settings.md). */
13
+ export * from './agent/index.js'
14
+
15
+ import { AiUsage } from './agent/ai-usage.js'
16
+ export const entities = [AiUsage]
12
17
  export * from './testing/tool-call-context.js'
@@ -58,6 +58,7 @@ export class AnthropicAIClient implements AIClient {
58
58
  ...(options.stopSequences && { stop_sequences: options.stopSequences }),
59
59
  messages: conversation
60
60
  } as any)
61
+ reportUsage(response, options, options.model ?? this.defaultModel)
61
62
 
62
63
  return extractResponseText(response)
63
64
  }
@@ -131,6 +132,7 @@ export class AnthropicAIClient implements AIClient {
131
132
  tools: sdkTools,
132
133
  ...(toolChoice && { tool_choice: toolChoice })
133
134
  } as any)
135
+ reportUsage(response, options, options.model ?? this.defaultModel)
134
136
 
135
137
  const text = extractResponseText(response) || undefined
136
138
  const toolCalls: AIToolCall[] = Array.isArray(response.content)
@@ -149,6 +151,21 @@ export class AnthropicAIClient implements AIClient {
149
151
  stopReason: normalizeAnthropicStopReason(response.stop_reason)
150
152
  }
151
153
  }
154
+
155
+ /** Every model Anthropic lists is a conversation model. */
156
+ async listModels(): Promise<string[]> {
157
+ const ids: string[] = []
158
+ for await (const model of this.sdk.models.list()) ids.push(model.id)
159
+ return ids.sort()
160
+ }
161
+ }
162
+
163
+ function reportUsage(response: any, options: AIChatOptions, requested: string): void {
164
+ options.onUsage?.({
165
+ model: response?.model ?? requested,
166
+ inputTokens: response?.usage?.input_tokens,
167
+ outputTokens: response?.usage?.output_tokens
168
+ })
152
169
  }
153
170
 
154
171
  // ── 변환 유틸 ─────────────────────────────────────────────────────────
@@ -34,17 +34,13 @@ export class GeminiAIClient implements AIClient {
34
34
  readonly id: string
35
35
  private sdk: GoogleGenerativeAI
36
36
  private defaultModel: string
37
+ private apiKey: string
38
+ private baseURL?: string
37
39
 
38
40
  constructor(config: AIClientConfig) {
39
- // Gemini SDK 는 환경변수 자동 인식이 없으므로 어댑터에서 명시적으로 fallback.
40
- // 우선순위: config.apiKey → GOOGLE_API_KEY → GEMINI_API_KEY → GOOGLE_GENERATIVE_AI_API_KEY
41
- const apiKey =
42
- config.apiKey ??
43
- process.env.GOOGLE_API_KEY ??
44
- process.env.GEMINI_API_KEY ??
45
- process.env.GOOGLE_GENERATIVE_AI_API_KEY ??
46
- ''
47
- this.sdk = new GoogleGenerativeAI(apiKey)
41
+ this.apiKey = config.apiKey
42
+ this.baseURL = config.baseURL
43
+ this.sdk = new GoogleGenerativeAI(config.apiKey)
48
44
  this.defaultModel = config.model ?? DEFAULT_MODEL
49
45
  this.id = `gemini:${this.defaultModel}`
50
46
  }
@@ -53,6 +49,7 @@ export class GeminiAIClient implements AIClient {
53
49
  const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)
54
50
  const model = this.buildModel(options, systemPrompt)
55
51
  const result = await model.generateContent({ contents } as any)
52
+ reportUsage(result, options, options.model ?? this.defaultModel)
56
53
  return result.response.text()
57
54
  }
58
55
 
@@ -72,8 +69,9 @@ export class GeminiAIClient implements AIClient {
72
69
  model: options.model ?? this.defaultModel,
73
70
  ...(systemPrompt && { systemInstruction: systemPrompt }),
74
71
  generationConfig
75
- })
72
+ }, this.requestOptions)
76
73
  const result = await model.generateContent({ contents } as any)
74
+ reportUsage(result, options, options.model ?? this.defaultModel)
77
75
  const text = result.response.text()
78
76
  return parseJSONResponse<T>(text)
79
77
  }
@@ -102,7 +100,7 @@ export class GeminiAIClient implements AIClient {
102
100
  model: options.model ?? this.defaultModel,
103
101
  ...(systemInstruction && { systemInstruction }),
104
102
  generationConfig: baseGenerationConfig(options)
105
- })
103
+ }, this.requestOptions)
106
104
  }
107
105
 
108
106
  async chatWithTools(
@@ -128,9 +126,10 @@ export class GeminiAIClient implements AIClient {
128
126
  generationConfig: baseGenerationConfig(options),
129
127
  tools: [{ functionDeclarations }] as any,
130
128
  ...(toolConfig && { toolConfig })
131
- } as any)
129
+ } as any, this.requestOptions)
132
130
 
133
131
  const result = await model.generateContent({ contents } as any)
132
+ reportUsage(result, options, options.model ?? this.defaultModel)
134
133
  const response: any = result.response
135
134
 
136
135
  const candidate = response?.candidates?.[0]
@@ -176,6 +175,49 @@ export class GeminiAIClient implements AIClient {
176
175
  stopReason: normalizeGeminiStopReason(candidate?.finishReason, toolCalls.length > 0)
177
176
  }
178
177
  }
178
+
179
+ /**
180
+ * The SDK has no model list, so this reads the REST one. A conversation model is one that answers
181
+ * generateContent; embedding and other models are left out.
182
+ */
183
+ async listModels(): Promise<string[]> {
184
+ const base = (this.baseURL || 'https://generativelanguage.googleapis.com').replace(/\/+$/, '')
185
+ const ids: string[] = []
186
+ let pageToken = ''
187
+ do {
188
+ const url = `${base}/v1beta/models?pageSize=1000${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`
189
+ const response = await fetch(url, { headers: { 'x-goog-api-key': this.apiKey } })
190
+ if (!response.ok) throw new ProviderListError(response.status, await response.text())
191
+ const page: any = await response.json()
192
+ for (const model of page.models ?? []) {
193
+ if ((model.supportedGenerationMethods ?? []).includes('generateContent')) {
194
+ ids.push(String(model.name).replace(/^models\//, ''))
195
+ }
196
+ }
197
+ pageToken = page.nextPageToken ?? ''
198
+ } while (pageToken)
199
+ return ids.sort()
200
+ }
201
+
202
+ private get requestOptions() {
203
+ return this.baseURL ? { baseUrl: this.baseURL } : undefined
204
+ }
205
+ }
206
+
207
+ /** A model list the provider refused, with the HTTP status the caller turns into a reason. */
208
+ export class ProviderListError extends Error {
209
+ constructor(readonly status: number, body: string) {
210
+ super(`model list failed with ${status}: ${body.slice(0, 200)}`)
211
+ }
212
+ }
213
+
214
+ function reportUsage(result: any, options: AIChatOptions, requested: string): void {
215
+ const usage = result?.response?.usageMetadata
216
+ options.onUsage?.({
217
+ model: result?.response?.modelVersion ?? requested,
218
+ inputTokens: usage?.promptTokenCount,
219
+ outputTokens: usage?.candidatesTokenCount
220
+ })
179
221
  }
180
222
 
181
223
  // ── 변환 유틸 ─────────────────────────────────────────────────────────
@@ -0,0 +1,30 @@
1
+ /*
2
+ * What this guards: OpenAI's model list has no capability field, so the agent's model picker keeps the conversation
3
+ * models by name. The rule lives in one place and this fixes what it keeps.
4
+ */
5
+ import { isOpenAIChatModel } from './openai'
6
+
7
+ it('★ keeps the GPT and o-series conversation models', () => {
8
+ for (const id of ['gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-5', 'chatgpt-4o-latest', 'o1', 'o3-mini', 'o4-mini']) {
9
+ expect([id, isOpenAIChatModel(id)]).toEqual([id, true])
10
+ }
11
+ })
12
+
13
+ it('★ leaves out embedding, speech, image, realtime, search and instruct models', () => {
14
+ for (const id of [
15
+ 'text-embedding-3-large',
16
+ 'tts-1',
17
+ 'whisper-1',
18
+ 'dall-e-3',
19
+ 'gpt-image-1',
20
+ 'gpt-4o-audio-preview',
21
+ 'gpt-4o-realtime-preview',
22
+ 'gpt-4o-transcribe',
23
+ 'gpt-4o-search-preview',
24
+ 'gpt-3.5-turbo-instruct',
25
+ 'omni-moderation-latest',
26
+ 'davinci-002'
27
+ ]) {
28
+ expect([id, isOpenAIChatModel(id)]).toEqual([id, false])
29
+ }
30
+ })
@@ -4,8 +4,7 @@
4
4
  * 기본 모델: gpt-4o (구성에서 override 가능).
5
5
  * SDK: openai
6
6
  *
7
- * 비고: legacy `openai` config 키 (codelingua 의 사용처) 도 factory.ts 가 이 어댑터로 자동 매핑.
8
- * organization 필드 지원.
7
+ * organization 필드 지원.
9
8
  */
10
9
  import OpenAI from 'openai'
11
10
  import type {
@@ -33,10 +32,8 @@ export class OpenAIAIClient implements AIClient {
33
32
  private defaultModel: string
34
33
 
35
34
  constructor(config: AIClientConfig) {
36
- // OpenAI SDK 는 apiKey 없을 시 OPENAI_API_KEY 환경변수 자동 인식.
37
- const apiKey = config.apiKey ?? process.env.OPENAI_API_KEY
38
35
  this.sdk = new OpenAI({
39
- apiKey,
36
+ apiKey: config.apiKey,
40
37
  ...(config.organization && { organization: config.organization }),
41
38
  ...(config.baseURL && { baseURL: config.baseURL })
42
39
  })
@@ -48,11 +45,13 @@ export class OpenAIAIClient implements AIClient {
48
45
  const sdkMessages = buildSDKMessages(messages, options.systemPrompt)
49
46
  const response = await this.sdk.chat.completions.create({
50
47
  model: options.model ?? this.defaultModel,
51
- max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
48
+ /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */
49
+ max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
52
50
  ...(options.temperature !== undefined && { temperature: options.temperature }),
53
51
  ...(options.stopSequences && { stop: options.stopSequences }),
54
52
  messages: sdkMessages as any
55
53
  })
54
+ reportUsage(response, options, options.model ?? this.defaultModel)
56
55
  return response.choices[0]?.message?.content ?? ''
57
56
  }
58
57
 
@@ -74,11 +73,13 @@ export class OpenAIAIClient implements AIClient {
74
73
  const sdkMessages = buildSDKMessages(messages, enforcedSystem)
75
74
  const response = await this.sdk.chat.completions.create({
76
75
  model: options.model ?? this.defaultModel,
77
- max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
76
+ /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */
77
+ max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
78
78
  ...(options.temperature !== undefined && { temperature: options.temperature }),
79
79
  response_format: { type: 'json_object' },
80
80
  messages: sdkMessages as any
81
81
  })
82
+ reportUsage(response, options, options.model ?? this.defaultModel)
82
83
  const text = response.choices[0]?.message?.content ?? '{}'
83
84
  return parseJSONResponse<T>(text)
84
85
  }
@@ -122,7 +123,8 @@ export class OpenAIAIClient implements AIClient {
122
123
 
123
124
  const response = await this.sdk.chat.completions.create({
124
125
  model: options.model ?? this.defaultModel,
125
- max_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
126
+ /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */
127
+ max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,
126
128
  ...(options.temperature !== undefined && { temperature: options.temperature }),
127
129
  ...(options.stopSequences && { stop: options.stopSequences }),
128
130
  messages: sdkMessages as any,
@@ -130,6 +132,7 @@ export class OpenAIAIClient implements AIClient {
130
132
  ...(toolChoice !== undefined && { tool_choice: toolChoice as any }),
131
133
  ...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })
132
134
  })
135
+ reportUsage(response, options, options.model ?? this.defaultModel)
133
136
 
134
137
  const choice = response.choices?.[0]
135
138
  const message = choice?.message
@@ -156,6 +159,29 @@ export class OpenAIAIClient implements AIClient {
156
159
  stopReason: normalizeOpenAIStopReason(choice?.finish_reason)
157
160
  }
158
161
  }
162
+
163
+ async listModels(): Promise<string[]> {
164
+ const ids: string[] = []
165
+ for await (const model of this.sdk.models.list()) ids.push(model.id)
166
+ return ids.filter(isOpenAIChatModel).sort()
167
+ }
168
+ }
169
+
170
+ /**
171
+ * OpenAI's model list has no capability field, so the conversation models are told apart by name: the GPT and
172
+ * o-series families, without their audio, realtime, speech, image, search and instruct variants. One rule, kept by a test.
173
+ */
174
+ export function isOpenAIChatModel(id: string): boolean {
175
+ if (!/^(gpt-|chatgpt-|o\d)/.test(id)) return false
176
+ return !/(audio|realtime|tts|transcribe|image|search|instruct|embedding|moderation)/.test(id)
177
+ }
178
+
179
+ function reportUsage(response: any, options: AIChatOptions, requested: string): void {
180
+ options.onUsage?.({
181
+ model: response?.model ?? requested,
182
+ inputTokens: response?.usage?.prompt_tokens,
183
+ outputTokens: response?.usage?.completion_tokens
184
+ })
159
185
  }
160
186
 
161
187
  // ── 변환 유틸 ─────────────────────────────────────────────────────────
@@ -80,6 +80,18 @@ export interface AIChatOptions {
80
80
  */
81
81
  systemPrompt?: string
82
82
  stopSequences?: string[]
83
+ /**
84
+ * Told what one provider call used, right after it returns. The metering boundary (aiClientFor) sets this per
85
+ * call so the usage row knows whose call it was; adapters call it once for every request they send.
86
+ */
87
+ onUsage?: (usage: AIProviderUsage) => void
88
+ }
89
+
90
+ /** What one provider request used, as the provider reported it. A count the provider did not report stays undefined. */
91
+ export interface AIProviderUsage {
92
+ model: string
93
+ inputTokens?: number
94
+ outputTokens?: number
83
95
  }
84
96
 
85
97
  export interface AIJSONOptions extends AIChatOptions {
@@ -180,14 +192,18 @@ export interface AIClient {
180
192
  tools: AITool[],
181
193
  options?: AIToolChatOptions
182
194
  ): Promise<AIToolChatResult>
195
+
196
+ /** The ids of the conversation models this key may call, sorted. Reading it also proves the key is accepted. */
197
+ listModels(): Promise<string[]>
183
198
  }
184
199
 
185
200
  export interface AIClientConfig {
186
201
  /** 등록된 provider 이름 (예: 'anthropic', 'openai', 'gemini') */
187
202
  provider: string
188
- /** 기본 모델 ID */
203
+ /** The model every call uses unless a call names another. */
189
204
  model?: string
190
- apiKey?: string
205
+ /** Required - a provider is never reached with a key from the process environment. */
206
+ apiKey: string
191
207
  /** OpenAI organization (provider:'openai' 전용) */
192
208
  organization?: string
193
209
  baseURL?: string
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "AI is not set up for this domain.",
3
+ "error.ai-agent-not-available": "The AI agent you picked is not available here.",
4
+ "error.ai-agent-provider-unknown": "\"{provider}\" is not a provider an agent can use.",
5
+ "error.ai-agent-key-required": "Enter the API key of \"{agent}\".",
6
+ "error.ai-agent-name-required": "Every agent needs a name.",
7
+ "error.ai-agent-model-required": "Pick the model of \"{agent}\".",
8
+ "error.ai-agent-model-not-offered": "The provider does not offer \"{model}\" to this key.",
9
+ "error.ai-agent-name-taken": "There is already an agent named \"{name}\".",
10
+ "error.ai-agent-not-in-domain": "That agent does not belong to this domain.",
11
+ "error.ai-provider-key-rejected": "The provider did not accept this API key.",
12
+ "error.ai-provider-unreachable": "The provider could not be reached."
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "La IA no está configurada para este dominio.",
3
+ "error.ai-agent-not-available": "El agente de IA elegido no está disponible aquí.",
4
+ "error.ai-agent-provider-unknown": "\"{provider}\" no es un proveedor que un agente pueda usar.",
5
+ "error.ai-agent-key-required": "Introduzca la clave de API de \"{agent}\".",
6
+ "error.ai-agent-name-required": "Cada agente necesita un nombre.",
7
+ "error.ai-agent-model-required": "Elija el modelo de \"{agent}\".",
8
+ "error.ai-agent-model-not-offered": "El proveedor no ofrece \"{model}\" a esta clave.",
9
+ "error.ai-agent-name-taken": "Ya existe un agente llamado \"{name}\".",
10
+ "error.ai-agent-not-in-domain": "Ese agente no pertenece a este dominio.",
11
+ "error.ai-provider-key-rejected": "El proveedor no aceptó esta clave de API.",
12
+ "error.ai-provider-unreachable": "No se pudo contactar con el proveedor."
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "この事業所には AI が設定されていません。",
3
+ "error.ai-agent-not-available": "選んだ AI エージェントはこの事業所では使えません。",
4
+ "error.ai-agent-provider-unknown": "「{provider}」はエージェントが使える提供元ではありません。",
5
+ "error.ai-agent-key-required": "「{agent}」の API キーを入力してください。",
6
+ "error.ai-agent-name-required": "エージェントごとに名前が必要です。",
7
+ "error.ai-agent-model-required": "「{agent}」のモデルを選んでください。",
8
+ "error.ai-agent-model-not-offered": "提供元はこのキーに「{model}」を提供していません。",
9
+ "error.ai-agent-name-taken": "「{name}」という名前のエージェントがすでにあります。",
10
+ "error.ai-agent-not-in-domain": "そのエージェントはこの事業所のものではありません。",
11
+ "error.ai-provider-key-rejected": "提供元がこの API キーを受け付けませんでした。",
12
+ "error.ai-provider-unreachable": "提供元に接続できませんでした。"
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "이 사업장에는 AI 가 설정되어 있지 않습니다.",
3
+ "error.ai-agent-not-available": "고른 AI 에이전트를 이 사업장에서 쓸 수 없습니다.",
4
+ "error.ai-agent-provider-unknown": "\"{provider}\" 은(는) 에이전트가 쓸 수 있는 제공처가 아닙니다.",
5
+ "error.ai-agent-key-required": "\"{agent}\" 의 API 키를 넣어 주세요.",
6
+ "error.ai-agent-name-required": "에이전트마다 이름이 있어야 합니다.",
7
+ "error.ai-agent-model-required": "\"{agent}\" 의 모델을 골라 주세요.",
8
+ "error.ai-agent-model-not-offered": "제공처가 이 키에 \"{model}\" 모델을 제공하지 않습니다.",
9
+ "error.ai-agent-name-taken": "\"{name}\" 이라는 에이전트가 이미 있습니다.",
10
+ "error.ai-agent-not-in-domain": "그 에이전트는 이 사업장의 것이 아닙니다.",
11
+ "error.ai-provider-key-rejected": "제공처가 이 API 키를 받지 않았습니다.",
12
+ "error.ai-provider-unreachable": "제공처에 닿지 않았습니다."
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "AI belum disediakan untuk domain ini.",
3
+ "error.ai-agent-not-available": "Ejen AI yang dipilih tidak tersedia di sini.",
4
+ "error.ai-agent-provider-unknown": "\"{provider}\" bukan penyedia yang boleh digunakan oleh ejen.",
5
+ "error.ai-agent-key-required": "Masukkan kunci API untuk \"{agent}\".",
6
+ "error.ai-agent-name-required": "Setiap ejen memerlukan nama.",
7
+ "error.ai-agent-model-required": "Pilih model untuk \"{agent}\".",
8
+ "error.ai-agent-model-not-offered": "Penyedia tidak menawarkan \"{model}\" kepada kunci ini.",
9
+ "error.ai-agent-name-taken": "Sudah ada ejen bernama \"{name}\".",
10
+ "error.ai-agent-not-in-domain": "Ejen itu bukan milik domain ini.",
11
+ "error.ai-provider-key-rejected": "Penyedia tidak menerima kunci API ini.",
12
+ "error.ai-provider-unreachable": "Penyedia tidak dapat dihubungi."
13
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "error.ai-not-configured": "此站点尚未设置 AI。",
3
+ "error.ai-agent-not-available": "所选的 AI 代理在此站点不可用。",
4
+ "error.ai-agent-provider-unknown": "“{provider}”不是代理可用的提供商。",
5
+ "error.ai-agent-key-required": "请输入“{agent}”的 API 密钥。",
6
+ "error.ai-agent-name-required": "每个代理都需要名称。",
7
+ "error.ai-agent-model-required": "请选择“{agent}”的模型。",
8
+ "error.ai-agent-model-not-offered": "提供商未向此密钥提供“{model}”。",
9
+ "error.ai-agent-name-taken": "已存在名为“{name}”的代理。",
10
+ "error.ai-agent-not-in-domain": "该代理不属于此站点。",
11
+ "error.ai-provider-key-rejected": "提供商未接受此 API 密钥。",
12
+ "error.ai-provider-unreachable": "无法连接到提供商。"
13
+ }