@things-factory/board-ai 10.1.154 → 10.1.156

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/board-ai",
3
- "version": "10.1.154",
3
+ "version": "10.1.156",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/client/index.js",
6
6
  "things-factory": true,
@@ -32,12 +32,12 @@
32
32
  "@operato/i18n": "^10.0.0",
33
33
  "@operato/scene-ops": "^10.14.1",
34
34
  "@operato/styles": "^10.0.0",
35
- "@things-factory/ai-assistant": "^10.1.154",
36
- "@things-factory/ai-client-base": "^10.1.149",
37
- "@things-factory/auth-base": "^10.1.154",
38
- "@things-factory/board-import": "^10.1.154",
35
+ "@things-factory/ai-assistant": "^10.1.156",
36
+ "@things-factory/ai-client-base": "^10.1.155",
37
+ "@things-factory/auth-base": "^10.1.155",
38
+ "@things-factory/board-import": "^10.1.156",
39
39
  "@things-factory/env": "^10.1.149",
40
- "@things-factory/shell": "^10.1.154",
40
+ "@things-factory/shell": "^10.1.155",
41
41
  "dompurify": "^3.0.0",
42
42
  "graphql-tag": "^2.12.6",
43
43
  "lit": "^3.1.2",
@@ -47,5 +47,5 @@
47
47
  "copyfiles": "^2.4.1",
48
48
  "rimraf": "^5.0.0"
49
49
  },
50
- "gitHead": "f28579aa7131cbb282eb8813f8b285d9d29bb525"
50
+ "gitHead": "641146444c8ac4e510854c40824457ffad5cf3f1"
51
51
  }
package/server/index.ts CHANGED
@@ -8,10 +8,10 @@
8
8
  *
9
9
  * @example
10
10
  * ```ts
11
- * import { createAIClient } from '@things-factory/ai-client-base'
11
+ * import { aiClientFor } from '@things-factory/ai-client-base'
12
12
  * import { DefaultBoardAIAssistant, applyScenePatch } from '@things-factory/board-ai'
13
13
  *
14
- * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
14
+ * const base = await aiClientFor({ domain, user }) // the domain's AI agent
15
15
  * const ai = new DefaultBoardAIAssistant(base, { scopes: ['fmsim'] })
16
16
  *
17
17
  * let board
@@ -9,9 +9,8 @@ import { GraphQLJSON } from 'graphql-scalars'
9
9
  import { MoreThan } from 'typeorm'
10
10
  import '@things-factory/auth-base'
11
11
 
12
- import { config } from '@things-factory/env'
13
- import { getRepository } from '@things-factory/shell'
14
- import { createAIClient, getDefaultAIClient, type AIClient } from '@things-factory/ai-client-base'
12
+ import { getRepository, rethrowSpoken } from '@things-factory/shell'
13
+ import { aiClientFor, type AgentClient, type AIClient } from '@things-factory/ai-client-base'
15
14
  import type { ComponentCategory } from '@things-factory/board-import'
16
15
 
17
16
  import { DefaultBoardAIAssistant } from './assistant.js'
@@ -185,9 +184,10 @@ class BoardAIChatInput {
185
184
 
186
185
  @Field({
187
186
  nullable: true,
188
- description: 'AI model identifier override (e.g. "claude-opus-4-7"). Falls back to the configured default when omitted.'
187
+ description:
188
+ 'The AI agent the person picked, from aiAgentChoices. Omitted takes the domain default. The provider, the model and the key are the agent\'s - never the request\'s.'
189
189
  })
190
- model?: string
190
+ agentId?: string
191
191
 
192
192
  @Field({
193
193
  nullable: true,
@@ -297,12 +297,21 @@ export class BoardAIChatResolver {
297
297
  @Arg('input') input: BoardAIChatInput,
298
298
  @Ctx() context: ResolverContext
299
299
  ): Promise<BoardAIChatOutput> {
300
- const base = resolveAIClient()
301
- if (!base) {
302
- throw new Error(
303
- 'No AI client configured. Set config.aiClient = { provider: "anthropic" | "gemini", model, apiKey } or call setDefaultAIClient(...).'
304
- )
300
+ /* The domain's agent - the one picked, or the default (design/ai/agent-settings.md §4). */
301
+ let base: AgentClient
302
+ try {
303
+ base = await aiClientFor({
304
+ domain: context.state.domain,
305
+ user: context.state.user,
306
+ agentId: input.agentId,
307
+ modeKind: input.mode?.kind,
308
+ sessionId: input.sessionId
309
+ })
310
+ } catch (error) {
311
+ rethrowSpoken(error, context)
305
312
  }
313
+ /* A tool that asks a model of its own within this turn answers with the same agent (toolCallContext). */
314
+ ;(context.state as any).aiAgentId = base.agent.id
306
315
 
307
316
  /*
308
317
  * A declared conversation decides its own authority (ADR-0061 decision 7): with a mode, the
@@ -457,7 +466,6 @@ export class BoardAIChatResolver {
457
466
  selectedRefids: input.selectedRefids,
458
467
  mentionPicks: input.mentions,
459
468
  componentSchemas: Array.isArray(input.componentSchemas) ? input.componentSchemas : undefined,
460
- model: modeSettings ? modeSettings.model : input.model,
461
469
  temperature: modeSettings ? modeSettings.temperature : input.temperature ?? undefined,
462
470
  maxTokens: modeSettings ? modeSettings.maxTokens : input.maxTokens ?? undefined,
463
471
  // 외부 도메인 (board-import 등) 의 read/external tool builder 가 server-side
@@ -527,6 +535,7 @@ export class BoardAIChatResolver {
527
535
 
528
536
  await sessionRepo.update(session.id, {
529
537
  aiClientId: base.id,
538
+ agentId: base.agent.id,
530
539
  updater: user,
531
540
  /* AI 응답도 세션 활동이다 — 이걸 빼면 다른 참여자에게 "새 소식 없음"으로 보인다. */
532
541
  ...activityPatch(enrichedReply)
@@ -721,12 +730,3 @@ export class BoardAIChatResolver {
721
730
  }
722
731
  }
723
732
 
724
- function resolveAIClient(): AIClient | undefined {
725
- const existing = getDefaultAIClient()
726
- if (existing) return existing
727
- const cfg = config.get('aiClient', null) as any
728
- if (cfg && cfg.provider) {
729
- return createAIClient(cfg)
730
- }
731
- return undefined
732
- }