@things-factory/ai-assistant 10.1.13 → 10.1.16

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 (31) hide show
  1. package/client/components/{board-ai-chat.ts → assistant-chat.ts} +589 -18
  2. package/client/components/chat-defaults.ts +24 -0
  3. package/client/components/chat-input-builder.ts +1 -1
  4. package/client/index.ts +2 -3
  5. package/dist-client/components/{board-ai-chat.d.ts → assistant-chat.d.ts} +39 -3
  6. package/dist-client/components/{board-ai-chat.js → assistant-chat.js} +597 -66
  7. package/dist-client/components/assistant-chat.js.map +1 -0
  8. package/dist-client/components/{board-ai-chat.test.js → assistant-chat.test.js} +1 -1
  9. package/dist-client/components/assistant-chat.test.js.map +1 -0
  10. package/dist-client/components/chat-defaults.d.ts +22 -0
  11. package/dist-client/components/chat-defaults.js +4 -0
  12. package/dist-client/components/chat-defaults.js.map +1 -1
  13. package/dist-client/components/chat-input-builder.js +1 -1
  14. package/dist-client/components/chat-input-builder.js.map +1 -1
  15. package/dist-client/index.d.ts +2 -3
  16. package/dist-client/index.js +2 -3
  17. package/dist-client/index.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/service/assistant-chat-resolver.js +12 -0
  20. package/dist-server/service/assistant-chat-resolver.js.map +1 -1
  21. package/dist-server/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +4 -4
  23. package/server/service/assistant-chat-resolver.ts +13 -0
  24. package/test/assistant-request-context.test.ts +1 -1
  25. package/test/assistant-session-toolbar.test.ts +1 -1
  26. package/translations/en.json +6 -1
  27. package/translations/ko.json +6 -1
  28. package/dist-client/components/board-ai-chat.js.map +0 -1
  29. package/dist-client/components/board-ai-chat.test.js.map +0 -1
  30. /package/client/components/{board-ai-chat.test.ts → assistant-chat.test.ts} +0 -0
  31. /package/dist-client/components/{board-ai-chat.test.d.ts → assistant-chat.test.d.ts} +0 -0
@@ -271,6 +271,18 @@ let AssistantChatResolver = class AssistantChatResolver {
271
271
  : undefined
272
272
  })
273
273
  : input.messages.map(message => ({ role: message.role, content: message.content }));
274
+ if (input.hostContext?.image?.data && conversation.length > 0) {
275
+ const last = conversation[conversation.length - 1];
276
+ if (last.role === 'user' && typeof last.content === 'string') {
277
+ const raw = input.hostContext.image;
278
+ const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\/[a-zA-Z]+;base64,/, '') : raw.data;
279
+ const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData);
280
+ last.content = [
281
+ { type: 'text', text: last.content },
282
+ { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }
283
+ ];
284
+ }
285
+ }
274
286
  const result = await (0, ai_client_base_1.registryAssistantChat)({
275
287
  client,
276
288
  messages: conversation,
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\nimport { createAIClient, getDefaultAIClient, registryAssistantChat, type AIClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field({\n description:\n 'Who the assistant is on this surface, in the words of the product that owns the screen. The rules for using each tool come from whoever registered it and are appended by the server.'\n })\n systemPrompt: string\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Registered tool categories this conversation may use (registerToolCategory names, e.g. [\"figure-authoring\"]). Omit to allow every registered category. An empty list allows none — a plain conversation.'\n })\n toolCategories?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field({\n nullable: true,\n description:\n 'Require a tool call before the model may answer. For surfaces that report live state, where an unchecked assertion reads the same as a checked one.'\n })\n requireGroundingTools?: boolean\n\n @Field({ nullable: true, description: 'Model identifier override. Falls back to the configured default.' })\n model?: string\n\n @Field({ nullable: true, description: 'Sampling temperature override.' })\n temperature?: number\n\n @Field({ nullable: true, description: 'Max output tokens per call.' })\n maxTokens?: number\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\nfunction resolveAIClient(): AIClient | undefined {\n const existing = getDefaultAIClient()\n if (existing) return existing\n const cfg = config.get('aiClient', null) as any\n if (cfg && cfg.provider) return createAIClient(cfg)\n return undefined\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\")')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const client = resolveAIClient()\n if (!client) {\n throw new Error(\n 'No AI client configured. Set config.aiClient = { provider, model, apiKey } or call setDefaultAIClient(...).'\n )\n }\n\n const { domain, user } = context.state\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error('이 대화는 다른 Figure의 이력입니다. 현재 모델의 대화를 다시 여세요.')\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error('애플리케이션 대화와 모델 대화를 구분해서 다시 여세요.')\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext)\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n })\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n const conversation = session\n ? buildLlmHistory(\n await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n }),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt: input.systemPrompt,\n toolCategories: input.toolCategories,\n hostContext: input.hostContext,\n state: context.state,\n model: input.model,\n temperature: input.temperature,\n maxTokens: input.maxTokens,\n requireToolOnFirstTurn: input.requireGroundingTools\n })\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: result.reply,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: result.reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n updater: user,\n ...activityPatch(result.reply)\n } as any)\n }\n\n return {\n reply: result.reply,\n clientId: client.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null\n }\n }\n}\n"]}
1
+ {"version":3,"file":"assistant-chat-resolver.js","sourceRoot":"","sources":["../../server/service/assistant-chat-resolver.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,+CAAoG;AACpG,qDAA6C;AAC7C,qCAAkC;AAElC,6CAA4C;AAC5C,iDAAqD;AACrD,mEAAyH;AAEzH,oEAA4D;AAC5D,sFAA8E;AAC9E,oEAA4D;AAC5D,kEAA+D;AAC/D,oFAA2E;AAC3E,4FAAmF;AACnF,sEAAgF;AAEhF;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAGhC,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;CAM1B,CAAA;AAJC;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;;mDAC1C;AAGZ;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC;;sDAC3B;AALX,qBAAqB;IAD1B,IAAA,wBAAS,GAAE;GACN,qBAAqB,CAM1B;AAGD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAuDvB,CAAA;AAlDC;IAJC,IAAA,oBAAK,EAAC;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,kEAAkE;KAChF,CAAC;;qDACgB;AAMlB;IAJC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,qBAAqB,CAAC,EAAE;QACpC,WAAW,EACT,2HAA2H;KAC9H,CAAC;;oDAC+B;AAMjC;IAJC,IAAA,oBAAK,EAAC;QACL,WAAW,EACT,uLAAuL;KAC1L,CAAC;;wDACkB;AAOpB;IALC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE;QACrB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,0MAA0M;KAC7M,CAAC;;0DACuB;AAOzB;IALC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QACxB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,oPAAoP;KACvP,CAAC;;uDACe;AAOjB;IALC,IAAA,oBAAK,EAAC;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,oLAAoL;KACvL,CAAC;;kEAC6B;AAO/B;IALC,IAAA,oBAAK,EAAC;QACL,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,qJAAqJ;KACxJ,CAAC;;iEAC6B;AAG/B;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kEAAkE,EAAE,CAAC;;iDAC7F;AAGd;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;;uDACrD;AAGpB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,6BAA6B,EAAE,CAAC;;qDACpD;AAtDd,kBAAkB;IADvB,IAAA,wBAAS,EAAC,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAAC;GACpF,kBAAkB,CAuDvB;AAGD,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CA0CxB,CAAA;AAxCC;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,8DAA8D,EAAE,CAAC;;kDAC1E;AAGb;IADC,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,wCAAwC,EAAE,CAAC;;qDACjD;AAGhB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,yCAAyC,EAAE,CAAC;;sDAChE;AAGlB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,0CAA0C,EAAE,CAAC;;0DAC7D;AAGtB;IADC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;;+DACnD;AAM3B;IAJC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QACxB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,iGAAiG;KAC/G,CAAC;;uDACc;AAOhB;IALC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE;QACrB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,gMAAgM;KACnM,CAAC;;yDACqB;AAOvB;IALC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QACxB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,iLAAiL;KACpL,CAAC;;sDACa;AAOf;IALC,IAAA,oBAAK,EAAC,GAAG,EAAE,CAAC,6BAAW,EAAE;QACxB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,uLAAuL;KAC1L,CAAC;;8DACqB;AAzCnB,mBAAmB;IADxB,IAAA,yBAAU,GAAE;GACP,mBAAmB,CA0CxB;AAED,SAAS,eAAe;IACtB,MAAM,QAAQ,GAAG,IAAA,mCAAkB,GAAE,CAAA;IACrC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAC7B,MAAM,GAAG,GAAG,YAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAQ,CAAA;IAC/C,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ;QAAE,OAAO,IAAA,+BAAc,EAAC,GAAG,CAAC,CAAA;IACnD,OAAO,SAAS,CAAA;AAClB,CAAC;AAGM,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;;;OAKG;IAWG,AAAN,KAAK,CAAC,aAAa,CAAe,KAAyB,EAAS,OAAY;QAC9E,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAChC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAA;QACH,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QACtC,MAAM,WAAW,GAAG,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAA;QAC9C,MAAM,WAAW,GAAG,IAAA,qBAAa,EAAC,6BAAW,CAAC,CAAA;QAE9C,IAAI,OAAgC,CAAA;QACpC,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAS,EAAE,CAAC,CAAA;YACpG,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,CAAC,SAAS,YAAY,CAAC,CAAA;YACvE,IAAI,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,KAAK,CAAC,WAAW,EAAE,QAAQ,KAAK,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACpF,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;YAC/D,CAAC;YACD,IAAI,KAAK,CAAC,UAAU,KAAK,aAAa,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,KAAK,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACrH,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;YACnD,CAAC;YACD,OAAO,GAAG,KAAK,CAAA;QACjB,CAAC;QAED,IAAA,+CAAoB,EAAC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAA;QAEhD,MAAM,eAAe,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACjE,IAAI,aAAiC,CAAA;QAErC,IAAI,OAAO,IAAI,eAAe,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;YAChD,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC;gBAC7C,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE;gBAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE;aAC7B,CAAC,CAAA;YACF,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC;gBACnC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS;gBAClC,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,eAAe,CAAC,OAAO;gBAChC,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,YAAY,CAAC,CAAC,CAAE,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAU,CAAC,CAAC,CAAC,SAAS;aAC3E,CAAC,CAAA;YACF,aAAa,GAAG,KAAK,CAAC,EAAE,CAAA;YAExB;;;;eAIG;YACH,IAAI,CAAC;gBACH,MAAM,KAAK,GAAQ,IAAA,gCAAa,EAAC,eAAe,CAAC,OAAO,CAAC,CAAA;gBACzD,IAAI,CAAC,OAAO,CAAC,IAAI;oBAAE,KAAK,CAAC,IAAI,GAAG,IAAA,kCAAe,EAAC,eAAe,CAAC,OAAO,CAAC,CAAA;gBACxE,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,EAAG,EAAE,KAAK,CAAC,CAAA;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,UAAU;YACZ,CAAC;YAED,IAAA,4CAAkB,EAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAS,CAAC,CAAA;YACzF,IAAA,oDAAsB,EAAC;gBACrB,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,SAAS,EAAE,OAAO,CAAC,EAAG;gBACtB,IAAI,EAAE,SAAS;gBACf,UAAU,EAAG,OAAe,CAAC,UAAU;gBACvC,QAAQ,EAAG,OAAe,CAAC,QAAQ;gBACnC,OAAO,EAAE,eAAe,CAAC,OAAO;gBAChC,OAAO,EAAE,IAAI,EAAE,EAAE;aAClB,CAAC,CAAA;QACJ,CAAC;QAED,MAAM,YAAY,GAAG,OAAO;YAC1B,CAAC,CAAC,IAAA,gCAAe,EACb,MAAM,WAAW,CAAC,IAAI,CAAC;gBACrB,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS,EAAE;gBAC7C,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;gBAC3B,SAAS,EAAE,CAAC,SAAS,CAAC;aACvB,CAAC,EACF;gBACE,QAAQ,EAAE,qBAAqB;gBAC/B,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;gBACpD,sFAAsF;gBACtF,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC3D,OAAO,EAAG,OAAe,CAAC,WAAW;oBACnC,CAAC,CAAC,EAAE,IAAI,EAAG,OAAe,CAAC,WAAW,EAAE,aAAa,EAAG,OAAe,CAAC,oBAAoB,EAAE;oBAC9F,CAAC,CAAC,SAAS;aACd,CACF;YACH,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAW,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QAE5F,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YAClD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAA;gBACnC,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAA;gBAChH,MAAM,GAAG,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBAC/F,IAAI,CAAC,OAAO,GAAG;oBACb,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE;oBACpC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,WAAW,EAAE,EAAE;iBAC3F,CAAA;YACV,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAA,sCAAqB,EAAC;YACzC,MAAM;YACN,QAAQ,EAAE,YAAmB;YAC7B,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,sBAAsB,EAAE,KAAK,CAAC,qBAAqB;SACpD,CAAC,CAAA;QAEF,IAAI,kBAAsC,CAAA;QAC1C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC;gBACpC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAS;gBAClC,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,MAAM,CAAC,KAAK;gBACrB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC/F,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC1G,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,IAAI;gBACb,aAAa,EAAE,aAAa,CAAC,CAAC,CAAE,EAAE,EAAE,EAAE,aAAa,EAAU,CAAC,CAAC,CAAC,SAAS;aAC1E,CAAC,CAAA;YACF,kBAAkB,GAAG,MAAM,CAAC,EAAE,CAAA;YAE9B,IAAA,4CAAkB,EAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAS,CAAC,CAAA;YAC1F,+FAA+F;YAC/F,IAAA,oDAAsB,EAAC;gBACrB,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,SAAS,EAAE,OAAO,CAAC,EAAG;gBACtB,IAAI,EAAE,SAAS;gBACf,UAAU,EAAG,OAAe,CAAC,UAAU;gBACvC,QAAQ,EAAG,OAAe,CAAC,QAAQ;gBACnC,OAAO,EAAE,MAAM,CAAC,KAAK;gBACrB,OAAO,EAAE,IAAI,EAAE,EAAE;aAClB,CAAC,CAAA;YAEF,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,EAAG,EAAE;gBACpC,UAAU,EAAE,MAAM,CAAC,EAAE;gBACrB,OAAO,EAAE,IAAI;gBACb,GAAG,IAAA,gCAAa,EAAC,MAAM,CAAC,KAAK,CAAC;aACxB,CAAC,CAAA;QACX,CAAC;QAED,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,SAAS,EAAE,OAAO,EAAE,EAAE;YACtB,aAAa;YACb,kBAAkB;YAClB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;YAC1E,YAAY,EAAE,MAAM,CAAC,YAAY;YACjC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI;YACvE,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI;SACtF,CAAA;IACH,CAAC;CACF,CAAA;AAjLY,sDAAqB;AAiB1B;IAVL,IAAA,uBAAQ,EAAC,GAAG,EAAE,CAAC,mBAAmB,EAAE;QACnC,WAAW,EACT,0JAA0J;KAC7J,CAAC;IACF;;;;OAIG;;IACF,IAAA,wBAAS,EAAC,6DAA6D,CAAC;IACpD,mBAAA,IAAA,kBAAG,EAAC,OAAO,CAAC,CAAA;IAA6B,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAA1B,kBAAkB;;0DA+J1D;gCAhLU,qBAAqB;IADjC,IAAA,uBAAQ,GAAE;GACE,qBAAqB,CAiLjC","sourcesContent":["/*\n * The neutral door into a conversation.\n *\n * ── Why this exists (2026-09-11) ───────────────────────────────────────────────\n * The chat component moved here from `board-ai`, but the only mutation it could call stayed\n * behind. operato-figure mounted the dock, registered four tools, and got\n * `Cannot query field \"boardAIChat\" on type \"Mutation\"` — it does not load board-ai. The twin has\n * the same shape and it does not show: its space deliberation runs through board-ai's door purely\n * because the board authoring half of the same product already loaded that package.\n *\n * ── Why the board resolver was not moved ───────────────────────────────────────\n * `board-ai-resolver.ts` is 692 lines and the larger part of it is board authoring itself:\n * `currentBoard` as a BoardModel, PatchEntry, board patch broadcasts, refid injection for\n * component mentions. Moving that here would teach this package the board document, which is the\n * dependency we just cut, pointing the other way.\n *\n * So the door moved and the editor did not. `boardAIChat` stays in board-ai for board authoring;\n * this one is for a surface that asks questions and runs registered tools.\n *\n * ── What this is not ───────────────────────────────────────────────────────────\n * It has no document, so it takes no document and returns no patch. Tools that write to a\n * document are not offered here — `registryAssistantChat` enforces that, and it decides what may\n * be dispatched from the same list it offers, so the two cannot drift apart.\n */\nimport { Arg, Ctx, Directive, Field, InputType, Mutation, ObjectType, Resolver } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\nimport '@things-factory/auth-base'\n\nimport { config } from '@things-factory/env'\nimport { getRepository } from '@things-factory/shell'\nimport { createAIClient, getDefaultAIClient, registryAssistantChat, type AIClient } from '@things-factory/ai-client-base'\n\nimport { ChatSession } from './chat-session/chat-session.js'\nimport { assertStationSession } from './chat-session/station-session-guard.js'\nimport { ChatMessage } from './chat-message/chat-message.js'\nimport { buildLlmHistory } from './chat-message/llm-history.js'\nimport { publishChatMessage } from './chat-message/chat-message-publish.js'\nimport { publishSessionActivity } from './chat-session/session-activity-publish.js'\nimport { activityPatch, autoSessionName } from './chat-session/session-inbox.js'\n\n/**\n * How many stored messages reach the model.\n *\n * A deliberation runs for days. Without a cap the prompt grows without bound and the oldest turns\n * bury the recent ones. Kept the same as the board conversation's cap so the two surfaces of one\n * product do not remember differently.\n */\nconst LLM_HISTORY_MAX_TURNS = 40\n\n@InputType()\nclass AssistantMessageInput {\n @Field({ description: \"Role: 'user' or 'assistant'\" })\n role: string\n\n @Field({ description: 'Message content' })\n content: string\n}\n\n@InputType({ description: 'One turn of a conversation that has no document of its own.' })\nclass AssistantChatInput {\n @Field({\n nullable: true,\n description: 'ChatSession id. Omit for a one-off question with no persistence.'\n })\n sessionId?: string\n\n @Field(() => [AssistantMessageInput], {\n description:\n 'Conversation, newest last. For a persisted session only the last user message is appended; the rest is read from storage.'\n })\n messages: AssistantMessageInput[]\n\n @Field({\n description:\n 'Who the assistant is on this surface, in the words of the product that owns the screen. The rules for using each tool come from whoever registered it and are appended by the server.'\n })\n systemPrompt: string\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Registered tool categories this conversation may use (registerToolCategory names, e.g. [\"figure-authoring\"]). Omit to allow every registered category. An empty list allows none — a plain conversation.'\n })\n toolCategories?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'What this surface is looking at (e.g. { figureId } while modelling). Tools read it so the model never has to ask the user for identifiers. Client-supplied — never used for authorization; a tool verifies the scope belongs to the caller tenant.'\n })\n hostContext?: any\n\n @Field({\n nullable: true,\n description:\n 'Last message to keep from the stored history. The client keeps its own truncation when a user edits a message, so pass the last kept id to hide the retracted span from the model.'\n })\n truncateAfterMessageId?: string\n\n @Field({\n nullable: true,\n description:\n 'Require a tool call before the model may answer. For surfaces that report live state, where an unchecked assertion reads the same as a checked one.'\n })\n requireGroundingTools?: boolean\n\n @Field({ nullable: true, description: 'Model identifier override. Falls back to the configured default.' })\n model?: string\n\n @Field({ nullable: true, description: 'Sampling temperature override.' })\n temperature?: number\n\n @Field({ nullable: true, description: 'Max output tokens per call.' })\n maxTokens?: number\n}\n\n@ObjectType()\nclass AssistantChatOutput {\n @Field({ description: 'The reply, including the notice when the loop stopped early.' })\n reply: string\n\n @Field({ description: 'AI client identifier (provider:model).' })\n clientId: string\n\n @Field({ nullable: true, description: 'Echo of the session id, when persisted.' })\n sessionId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the user input.' })\n userMessageId?: string\n\n @Field({ nullable: true, description: 'Stored ChatMessage id of the reply.' })\n assistantMessageId?: string\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description: 'Tool usages during the turn — {name, arguments, result, kind}. The surface shows them foldably.'\n })\n toolUsages?: any\n\n @Field(() => [String], {\n nullable: true,\n description:\n 'Tool names offered to the model this turn. A surface that expected a category and sees it missing here knows the registration did not happen, rather than reading a vague answer as a refusal.'\n })\n offeredTools?: string[]\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Actions a tool recorded without executing — each carries what the tool returned alongside `proposed: true`. The surface renders a confirm button; executing is the user\\'s act.'\n })\n proposals?: any\n\n @Field(() => GraphQLJSON, {\n nullable: true,\n description:\n 'Identifiers in the reply that appear in nothing the model received. Hallucination candidates surfaced to the user; the reply itself is not altered. Null when the answer is grounded.'\n })\n groundingWarnings?: any\n}\n\nfunction resolveAIClient(): AIClient | undefined {\n const existing = getDefaultAIClient()\n if (existing) return existing\n const cfg = config.get('aiClient', null) as any\n if (cfg && cfg.provider) return createAIClient(cfg)\n return undefined\n}\n\n@Resolver()\nexport class AssistantChatResolver {\n /*\n * No `@transaction`, for the same reason the board conversation has none: a model call takes\n * seconds to tens of seconds, and holding a write lock for that long collides with everything\n * else on SQLite. Writes are short and around the call. For a conversation, a partly stored\n * history is safer than a lost one.\n */\n @Mutation(() => AssistantChatOutput, {\n description:\n 'Ask the assistant a question on a surface that has no document of its own. Tools come from the registry; with a sessionId the conversation is persisted.'\n })\n /*\n * `ai-assistant:mutation` — the same door as the board conversation's, which moved here on\n * 2026-09-10. No `legacy` alias: this operation is new, so no one was ever passing under an old\n * name, and declaring an alias would open a second name that nothing needs.\n */\n @Directive('@privilege(category: \"ai-assistant\", privilege: \"mutation\")')\n async assistantChat(@Arg('input') input: AssistantChatInput, @Ctx() context: any): Promise<AssistantChatOutput> {\n const client = resolveAIClient()\n if (!client) {\n throw new Error(\n 'No AI client configured. Set config.aiClient = { provider, model, apiKey } or call setDefaultAIClient(...).'\n )\n }\n\n const { domain, user } = context.state\n const sessionRepo = getRepository(ChatSession)\n const messageRepo = getRepository(ChatMessage)\n\n let session: ChatSession | undefined\n if (input.sessionId) {\n const found = await sessionRepo.findOneBy({ id: input.sessionId, domain: { id: domain.id } as any })\n if (!found) throw new Error(`ChatSession ${input.sessionId} not found`)\n if (found.anchorType === 'figure' && input.hostContext?.figureId !== found.anchorId) {\n throw new Error('이 대화는 다른 Figure의 이력입니다. 현재 모델의 대화를 다시 여세요.')\n }\n if (found.anchorType === 'application' && (input.hostContext?.app !== found.anchorId || input.hostContext?.figureId)) {\n throw new Error('애플리케이션 대화와 모델 대화를 구분해서 다시 여세요.')\n }\n session = found\n }\n\n assertStationSession(session, input.hostContext)\n\n const lastUserMessage = input.messages[input.messages.length - 1]\n let userMessageId: string | undefined\n\n if (session && lastUserMessage?.role === 'user') {\n const previousLast = await messageRepo.findOne({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'DESC' }\n })\n const saved = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'user',\n content: lastUserMessage.content,\n creator: user,\n updater: user,\n parentMessage: previousLast ? ({ id: previousLast.id } as any) : undefined\n })\n userMessageId = saved.id\n\n /*\n * Session activity is denormalised for the list: most-recent ordering and another\n * participant's unread badge read it. Best effort — a conversation must not fail because a\n * list sorts one position late.\n */\n try {\n const patch: any = activityPatch(lastUserMessage.content)\n if (!session.name) patch.name = autoSessionName(lastUserMessage.content)\n await sessionRepo.update(session.id!, patch)\n } catch {\n /* noop */\n }\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: saved } as any)\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: lastUserMessage.content,\n actorId: user?.id\n })\n }\n\n const conversation = session\n ? buildLlmHistory(\n await messageRepo.find({\n where: { session: { id: session.id } as any },\n order: { createdAt: 'ASC' },\n relations: ['creator']\n }),\n {\n maxTurns: LLM_HISTORY_MAX_TURNS,\n truncateAfterMessageId: input.truncateAfterMessageId,\n /* The message just stored is this turn's question — truncation must never drop it. */\n keepMessageIds: userMessageId ? [userMessageId] : undefined,\n summary: (session as any).lastSummary\n ? { text: (session as any).lastSummary, upToMessageId: (session as any).summaryUpToMessageId }\n : undefined\n }\n )\n : input.messages.map(message => ({ role: message.role as any, content: message.content }))\n\n if (input.hostContext?.image?.data && conversation.length > 0) {\n const last = conversation[conversation.length - 1]\n if (last.role === 'user' && typeof last.content === 'string') {\n const raw = input.hostContext.image\n const rawData = typeof raw.data === 'string' ? raw.data.replace(/^data:image\\/[a-zA-Z]+;base64,/, '') : raw.data\n const buf = typeof rawData === 'string' ? Buffer.from(rawData, 'base64') : Buffer.from(rawData)\n last.content = [\n { type: 'text', text: last.content },\n { type: 'image', source: { kind: 'base64', data: buf, mediaType: raw.mediaType || 'image/png' } }\n ] as any\n }\n }\n\n const result = await registryAssistantChat({\n client,\n messages: conversation as any,\n systemPrompt: input.systemPrompt,\n toolCategories: input.toolCategories,\n hostContext: input.hostContext,\n state: context.state,\n model: input.model,\n temperature: input.temperature,\n maxTokens: input.maxTokens,\n requireToolOnFirstTurn: input.requireGroundingTools\n })\n\n let assistantMessageId: string | undefined\n if (session) {\n const stored = await messageRepo.save({\n session: { id: session.id } as any,\n role: 'assistant',\n content: result.reply,\n toolUsages: result.loop.toolUsages?.length ? JSON.stringify(result.loop.toolUsages) : undefined,\n groundingWarnings: result.groundingWarnings?.length ? JSON.stringify(result.groundingWarnings) : undefined,\n creator: user,\n updater: user,\n parentMessage: userMessageId ? ({ id: userMessageId } as any) : undefined\n })\n assistantMessageId = stored.id\n\n publishChatMessage({ domainId: domain.id, sessionId: session.id, message: stored } as any)\n /* The reply is session activity too. Leaving it out shows other participants \"nothing new\". */\n publishSessionActivity({\n domainId: domain.id,\n sessionId: session.id!,\n kind: 'message',\n anchorType: (session as any).anchorType,\n anchorId: (session as any).anchorId,\n preview: result.reply,\n actorId: user?.id\n })\n\n await sessionRepo.update(session.id!, {\n aiClientId: client.id,\n updater: user,\n ...activityPatch(result.reply)\n } as any)\n }\n\n return {\n reply: result.reply,\n clientId: client.id,\n sessionId: session?.id,\n userMessageId,\n assistantMessageId,\n toolUsages: result.loop.toolUsages?.length ? result.loop.toolUsages : null,\n offeredTools: result.offeredTools,\n proposals: result.loop.proposals?.length ? result.loop.proposals : null,\n groundingWarnings: result.groundingWarnings?.length ? result.groundingWarnings : null\n }\n }\n}\n"]}