@things-factory/ai-client-base 10.1.203 → 10.1.206
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/dist-server/service/assistant/registry-assistant.d.ts +15 -1
- package/dist-server/service/assistant/registry-assistant.js +18 -5
- package/dist-server/service/assistant/registry-assistant.js.map +1 -1
- package/dist-server/service/providers/anthropic.js +3 -2
- package/dist-server/service/providers/anthropic.js.map +1 -1
- package/dist-server/service/providers/gemini.js +4 -3
- package/dist-server/service/providers/gemini.js.map +1 -1
- package/dist-server/service/providers/openai.js +4 -3
- package/dist-server/service/providers/openai.js.map +1 -1
- package/dist-server/service/providers/request-options.d.ts +4 -0
- package/dist-server/service/providers/request-options.js +7 -0
- package/dist-server/service/providers/request-options.js.map +1 -0
- package/dist-server/service/testing/tool-call-context.d.ts +3 -1
- package/dist-server/service/testing/tool-call-context.js +2 -0
- package/dist-server/service/testing/tool-call-context.js.map +1 -1
- package/dist-server/service/tool-registry/index.d.ts +1 -1
- package/dist-server/service/tool-registry/index.js.map +1 -1
- package/dist-server/service/tool-registry/types.d.ts +16 -0
- package/dist-server/service/tool-registry/types.js.map +1 -1
- package/dist-server/service/types.d.ts +5 -0
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/server/service/assistant/registry-assistant.test.ts +80 -0
- package/server/service/assistant/registry-assistant.ts +33 -7
- package/server/service/providers/anthropic.ts +3 -2
- package/server/service/providers/gemini.ts +4 -3
- package/server/service/providers/openai.ts +4 -3
- package/server/service/providers/request-options.ts +12 -0
- package/server/service/providers/request-signal.test.ts +77 -0
- package/server/service/testing/tool-call-context.ts +4 -1
- package/server/service/tool-registry/index.ts +1 -0
- package/server/service/tool-registry/types.ts +17 -0
- package/server/service/types.ts +5 -0
|
@@ -2,7 +2,8 @@ import type { AIClient, AIMessage } from '../types.js';
|
|
|
2
2
|
import { type ToolAccessCheck } from './tool-scope.js';
|
|
3
3
|
import { type AssistantChatResult } from './assistant-chat.js';
|
|
4
4
|
import type { GroundingSources } from '../utils/index.js';
|
|
5
|
-
import type { ToolAttachment } from '../tool-registry/types.js';
|
|
5
|
+
import type { ToolAttachment, UnattendedRunRef } from '../tool-registry/types.js';
|
|
6
|
+
import { type ToolCallRecord } from '../tool-registry/call-recorder.js';
|
|
6
7
|
/** Lookups a registry conversation may take before it answers from what it has (operato-twin's assistant takes 16 too). */
|
|
7
8
|
export declare const REGISTRY_MAX_ITERATIONS = 16;
|
|
8
9
|
export interface RegistryAssistantInput {
|
|
@@ -57,6 +58,19 @@ export interface RegistryAssistantInput {
|
|
|
57
58
|
attachments?: ToolAttachment[];
|
|
58
59
|
/** Texts the answer is allowed to draw identifiers from, besides the tool results. */
|
|
59
60
|
groundingSources?: Omit<GroundingSources, 'toolResults'>;
|
|
61
|
+
/** Stops the loop and the provider request - an unattended run's time limit. */
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
/** Lookups before the answer; `REGISTRY_MAX_ITERATIONS` when absent. */
|
|
64
|
+
maxIterations?: number;
|
|
65
|
+
/** At the lookup limit, one more call without tools to answer from what was read. On when absent. */
|
|
66
|
+
salvageOnMaxIterations?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Each call as the recorders get it (`digestOf`, never its raw values) - an unattended run lists the reads its draft
|
|
69
|
+
* rested on from the same digest the audit keeps, so the two cannot describe one call differently.
|
|
70
|
+
*/
|
|
71
|
+
onToolCall?: (record: ToolCallRecord) => void;
|
|
72
|
+
/** Set for an unattended run; tools and recorders see it as `context.run`. */
|
|
73
|
+
run?: UnattendedRunRef;
|
|
60
74
|
}
|
|
61
75
|
export interface RegistryAssistantResult extends AssistantChatResult<unknown> {
|
|
62
76
|
/** Tool names actually offered to the model this turn. The caller may log or return it. */
|
|
@@ -51,7 +51,19 @@ async function registryAssistantChat(input) {
|
|
|
51
51
|
...(input.mode ? { mode: input.mode } : {}),
|
|
52
52
|
/* 번역기와 첨부는 서버의 값이다 — `host` 에서 받지 않는다(ADR-0061 2026-10-16 판정 1·2). */
|
|
53
53
|
t: input.t ?? translate_js_1.defaultValueTranslate,
|
|
54
|
-
attachments: input.attachments ?? []
|
|
54
|
+
attachments: input.attachments ?? [],
|
|
55
|
+
...(input.run ? { run: input.run } : {})
|
|
56
|
+
};
|
|
57
|
+
const recorded = async (run, context) => {
|
|
58
|
+
if (input.onToolCall) {
|
|
59
|
+
try {
|
|
60
|
+
input.onToolCall((0, call_recorder_js_1.digestOf)(run));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
console.warn(`[registry-assistant] onToolCall failed for ${run.tool}:`, e?.message ?? e);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await (0, call_recorder_js_1.recordToolCall)(run, context);
|
|
55
67
|
};
|
|
56
68
|
const result = await (0, assistant_chat_js_1.assistantChat)({
|
|
57
69
|
initialConversation: messages,
|
|
@@ -66,8 +78,9 @@ async function registryAssistantChat(input) {
|
|
|
66
78
|
* A question across a product's records takes many lookups. At the limit the model answers from
|
|
67
79
|
* what it read and says what is missing — the person is never told to split the question.
|
|
68
80
|
*/
|
|
69
|
-
maxIterations: exports.REGISTRY_MAX_ITERATIONS,
|
|
70
|
-
salvageOnMaxIterations: true
|
|
81
|
+
maxIterations: input.maxIterations ?? exports.REGISTRY_MAX_ITERATIONS,
|
|
82
|
+
salvageOnMaxIterations: input.salvageOnMaxIterations ?? true,
|
|
83
|
+
signal: input.signal
|
|
71
84
|
},
|
|
72
85
|
chat: (msgs, toolDefs, opts) => client.chatWithTools(msgs, toolDefs, opts),
|
|
73
86
|
context: toolContext,
|
|
@@ -89,11 +102,11 @@ async function registryAssistantChat(input) {
|
|
|
89
102
|
const record = { tool: spec.name, category: categoryOf.get(spec.name) ?? '', args };
|
|
90
103
|
try {
|
|
91
104
|
const result = await spec.builder(args, context);
|
|
92
|
-
await (
|
|
105
|
+
await recorded({ ...record, result, durationMs: Date.now() - started }, context);
|
|
93
106
|
return result;
|
|
94
107
|
}
|
|
95
108
|
catch (e) {
|
|
96
|
-
await (
|
|
109
|
+
await recorded({ ...record, error: e, durationMs: Date.now() - started }, context);
|
|
97
110
|
throw e;
|
|
98
111
|
}
|
|
99
112
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-assistant.js","sourceRoot":"","sources":["../../../server/service/assistant/registry-assistant.ts"],"names":[],"mappings":";;;AAiGA,sDAmGC;AA7KD,mDAA4G;AAC5G,2DAA6E;AAC7E,iDAA8C;AAG9C,gEAAqE;AACrE,wEAAkE;AAGlE,2HAA2H;AAC9G,QAAA,uBAAuB,GAAG,EAAE,CAAA;AA0DzC;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CAAC,KAA6B;IACvE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IAEpF,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,kGAAkG,CAAC,CAAA;IACrH,CAAC;IAED;;;;;OAKG;IACH,MAAM,KAAK,GAAG,IAAA,+BAAe,EAC3B,MAAM,IAAA,gCAAgB,EAAC,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,EACxD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAC7B,CAAA;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAqB,CAAC,CAAC,CAAC,CAAA;IAE5I,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QACxF,UAAU,EAAE,IAAI,CAAC,MAAM;KACxB,CAAC,CAAC,CAAA;IAEH;;;OAGG;IACH,MAAM,QAAQ,GAAG,IAAA,iCAAiB,EAAC,KAAK,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAA;IAEjG;;;OAGG;IACH,MAAM,WAAW,GAAoB;QACnC,KAAK;QACL,IAAI,EAAE,WAAW,IAAI,EAAE;QACvB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,sEAAsE;QACtE,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,oCAAqB;QACnC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;KACrC,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,IAAA,iCAAa,EAA2B;QAC3D,mBAAmB,EAAE,QAAQ;QAC7B,KAAK;QACL,OAAO,EAAE;YACP,YAAY,EAAE,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;YACpD;;;eAGG;YACH,aAAa,EAAE,+BAAuB;YACtC,sBAAsB,EAAE,IAAI;SAC7B;QACD,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,aAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC;QAC3E,OAAO,EAAE,WAAW;QACpB,QAAQ,EAAE;YACR,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YACpC;;;eAGG;YACH,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK;YACxB,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK;YACzB,gHAAgH;YAChH,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;gBACvC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAClC,IAAI,CAAC,IAAI;oBAAE,OAAO,SAAS,CAAA;gBAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAA;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;gBAC1B,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAA;gBACnF,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAO,IAAI,CAAC,OAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;oBACzD,MAAM,IAAA,iCAAc,EAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,CAAA;oBACtF,OAAO,MAAM,CAAA;gBACf,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,MAAM,IAAA,iCAAc,EAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,CAAA;oBACxF,MAAM,CAAC,CAAA;gBACT,CAAC;YACH,CAAC;YACD,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,+CAA+C,CAAC,EAAE,CAAC;YAC1G,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI;YACxB,sHAAsH;YACtH,mBAAmB,EAAE,4BAAa;SACnC;QACD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,CAAC,EAAE,KAAK,CAAC,CAAC;KACX,CAAC,CAAA;IAEF,OAAO,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAClE,CAAC","sourcesContent":["/*\n * A conversation for a product that has no editor of its own.\n *\n * ── What this is for ───────────────────────────────────────────────────────────\n * `board-ai` assembles its conversation from two sources: its own board editing tools, and\n * whatever other packages registered in the tool registry. The second half is the same work for\n * every product — read the specs, build the definitions, dispatch by name — but it only existed\n * inside the board assistant, so a product without a board had nowhere to ask a question from.\n * operato-figure hit exactly that: the dock was mounted, the tools were registered, and the only\n * mutation that could run them lived in a package figure does not load.\n *\n * ── Read only ──────────────────────────────────────────────────────────────────\n * A registry category may also carry `write` and `action` tools. Those produce edit ops for a\n * document the host owns, and a host with no document cannot apply them. Offering them here would\n * declare a door with no road behind it — the model would call one and the call would go nowhere.\n * `external` tools change something outside the conversation (board-import's\n * `materializeImportSession` creates a Board) and this door has no confirm-and-apply step, so they\n * are not offered either (A1, 2026-09-17). This runner offers, and dispatches, `read` only.\n *\n * The gate answers from the same list the definitions are built from, so what is offered and what\n * may be dispatched cannot drift apart.\n */\nimport type { AIClient, AIMessage } from '../types.js'\nimport { narrowToolScope, resolveToolScope, toolScopeGuidance, type ToolAccessCheck } from './tool-scope.js'\nimport { assistantChat, type AssistantChatResult } from './assistant-chat.js'\nimport { resultShapeOf } from './summarize.js'\nimport type { GroundingSources } from '../utils/index.js'\nimport type { ToolAttachment, ToolCallContext } from '../tool-registry/types.js'\nimport { defaultValueTranslate } from '../tool-registry/translate.js'\nimport { recordToolCall } from '../tool-registry/call-recorder.js'\n\n\n/** Lookups a registry conversation may take before it answers from what it has (operato-twin's assistant takes 16 too). */\nexport const REGISTRY_MAX_ITERATIONS = 16\n\nexport interface RegistryAssistantInput {\n /** Provider client. Must implement `chatWithTools`. */\n client: AIClient\n /** The conversation so far, newest last. The caller owns history and truncation. */\n messages: AIMessage[]\n /** Who the assistant is and what this surface is. The host owns this prose. */\n systemPrompt: string\n /**\n * Categories this conversation may use. **Omitting it allows nothing** — the same as an empty\n * list, a plain conversation with no tools (A1, 2026-09-17). It used to mean every registered\n * category, which is how a Board-creating tool reached any conversation.\n */\n toolCategories?: string[]\n /**\n * Answers whether the caller holds a tool's declared privilege. Without it, every tool that\n * declares a privilege stays closed. The host builds it from the request — this package does\n * not know auth.\n */\n toolAccess?: ToolAccessCheck\n /** Passed to tools as `context.host`. Client-supplied — never used for authorization. */\n hostContext?: any\n /**\n * Which conversation this is — `{ kind, target }` from the registered mode (ADR-0061 decision 3).\n *\n * Tools see it as `context.mode`. It is the server's value, resolved from the registry before the\n * turn runs, so a tool that behaves differently in two conversations reads it here and not from\n * `host`, which the client fills.\n */\n mode?: { kind: string; target?: string }\n /** Caller's koa state (domain, user, tx). Tools that read the database need it. */\n state: any\n model?: string\n temperature?: number\n maxTokens?: number\n /** Make the model call a tool before it may answer. For surfaces that report live state. */\n requireToolOnFirstTurn?: boolean\n /**\n * The request's translator — the stop notice is then in the reader's language, and tools write what a person\n * reads with it (`context.t`). Without it tools get `defaultValueTranslate`: the English default values.\n */\n t?: (key: string, params?: Record<string, any>) => string\n /**\n * The files sent with this turn, after the caller's server checked them — stored with this conversation, the\n * conversation the caller's to write, the mode's policy met (ADR-0061, 2026-10-16 decision 2). Tools see them as\n * `context.attachments`. Never built from the client's request: what a client puts in `hostContext` stays there.\n */\n attachments?: ToolAttachment[]\n /** Texts the answer is allowed to draw identifiers from, besides the tool results. */\n groundingSources?: Omit<GroundingSources, 'toolResults'>\n}\n\nexport interface RegistryAssistantResult extends AssistantChatResult<unknown> {\n /** Tool names actually offered to the model this turn. The caller may log or return it. */\n offeredTools: string[]\n}\n\n/**\n * Run one turn of a conversation whose only tools come from the registry.\n *\n * Throws when the client cannot use tools at all — a surface built on registered tools that\n * silently ran without them would answer from nothing and look like it had checked.\n */\nexport async function registryAssistantChat(input: RegistryAssistantInput): Promise<RegistryAssistantResult> {\n const { client, messages, systemPrompt, toolCategories, hostContext, state } = input\n\n if (!client.chatWithTools) {\n throw new Error('[registry-assistant] the configured AI client cannot call tools, so this conversation cannot run')\n }\n\n /*\n * Only `read` is offered here. `external` means the tool changes something outside the\n * conversation (board-import's `materializeImportSession` creates a Board); this door answers\n * questions and has no confirm-and-apply step, so a tool with an effect does not go through it\n * even when the caller holds the privilege.\n */\n const scope = narrowToolScope(\n await resolveToolScope(toolCategories, input.toolAccess),\n spec => spec.kind === 'read'\n )\n const specs = scope.specs\n const byName = new Map(specs.map(spec => [spec.name, spec]))\n const categoryOf = new Map(scope.categories.flatMap(category => category.specs.map(spec => [spec.name, category.name] as [string, string])))\n\n const tools = specs.map(spec => ({\n name: spec.name,\n description: spec.themeNote ? `${spec.description} ${spec.themeNote}` : spec.description,\n parameters: spec.schema\n }))\n\n /*\n * The rules for using a tool belong to whoever registered it. Collect only the exposed ones, so\n * a surface never reads instructions for a category it cannot call.\n */\n const guidance = toolScopeGuidance(scope)\n const prompt = guidance.length > 0 ? `${systemPrompt}\\n\\n${guidance.join('\\n\\n')}` : systemPrompt\n\n /*\n * `mode` 는 서버의 값이다 — 등록된 모드에서 왔고, `host` 와 달리 클라이언트가 못 고친다. A2 가 서기\n * 전에는 채울 것이 없어 비워 두었다(ADR-0061 결정 3 의 주석). 이제 부르는 쪽이 들고 있다.\n */\n const toolContext: ToolCallContext = {\n state,\n host: hostContext ?? {},\n ...(input.mode ? { mode: input.mode } : {}),\n /* 번역기와 첨부는 서버의 값이다 — `host` 에서 받지 않는다(ADR-0061 2026-10-16 판정 1·2). */\n t: input.t ?? defaultValueTranslate,\n attachments: input.attachments ?? []\n }\n\n const result = await assistantChat<ToolCallContext, unknown>({\n initialConversation: messages,\n tools,\n options: {\n systemPrompt: prompt,\n model: input.model,\n temperature: input.temperature,\n maxTokens: input.maxTokens,\n requireToolOnFirstTurn: input.requireToolOnFirstTurn,\n /*\n * A question across a product's records takes many lookups. At the limit the model answers from\n * what it read and says what is missing — the person is never told to split the question.\n */\n maxIterations: REGISTRY_MAX_ITERATIONS,\n salvageOnMaxIterations: true\n },\n chat: (msgs, toolDefs, opts) => client.chatWithTools!(msgs, toolDefs, opts),\n context: toolContext,\n dispatch: {\n isReadTool: name => byName.has(name),\n /*\n * This surface has no document, so nothing here can write. Saying so plainly makes the loop\n * report an unknown name back to the model instead of accepting a call and dropping it.\n */\n isWriteTool: () => false,\n isActionTool: () => false,\n /* Every call is handed to the recorders - an application's audit sees the tools other packages declared too. */\n executeReadTool: async (call, context) => {\n const spec = byName.get(call.name)\n if (!spec) return undefined\n const args = call.arguments ?? {}\n const started = Date.now()\n const record = { tool: spec.name, category: categoryOf.get(spec.name) ?? '', args }\n try {\n const result = await (spec.builder as any)(args, context)\n await recordToolCall({ ...record, result, durationMs: Date.now() - started }, context)\n return result\n } catch (e) {\n await recordToolCall({ ...record, error: e, durationMs: Date.now() - started }, context)\n throw e\n }\n },\n validateWriteToolCall: () => ({ valid: false, errors: ['this conversation has no document to write to'] }),\n toolCallToOp: () => null,\n /* The conversation row keeps the shape of each answer, as the audit does - never its values (ADR-0061 decision 8). */\n summarizeToolResult: resultShapeOf\n },\n groundingSources: input.groundingSources,\n t: input.t\n })\n\n return { ...result, offeredTools: tools.map(tool => tool.name) }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"registry-assistant.js","sourceRoot":"","sources":["../../../server/service/assistant/registry-assistant.ts"],"names":[],"mappings":";;;AA8GA,sDAgHC;AAvMD,mDAA4G;AAC5G,2DAA6E;AAC7E,iDAA8C;AAG9C,gEAAqE;AACrE,wEAAmH;AAGnH,2HAA2H;AAC9G,QAAA,uBAAuB,GAAG,EAAE,CAAA;AAuEzC;;;;;GAKG;AACI,KAAK,UAAU,qBAAqB,CAAC,KAA6B;IACvE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IAEpF,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,kGAAkG,CAAC,CAAA;IACrH,CAAC;IAED;;;;;OAKG;IACH,MAAM,KAAK,GAAG,IAAA,+BAAe,EAC3B,MAAM,IAAA,gCAAgB,EAAC,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,EACxD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAC7B,CAAA;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAA;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAqB,CAAC,CAAC,CAAC,CAAA;IAE5I,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/B,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;QACxF,UAAU,EAAE,IAAI,CAAC,MAAM;KACxB,CAAC,CAAC,CAAA;IAEH;;;OAGG;IACH,MAAM,QAAQ,GAAG,IAAA,iCAAiB,EAAC,KAAK,CAAC,CAAA;IACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,OAAO,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAA;IAEjG;;;OAGG;IACH,MAAM,WAAW,GAAoB;QACnC,KAAK;QACL,IAAI,EAAE,WAAW,IAAI,EAAE;QACvB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3C,sEAAsE;QACtE,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,oCAAqB;QACnC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;QACpC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzC,CAAA;IAED,MAAM,QAAQ,GAAG,KAAK,EAAE,GAAgB,EAAE,OAAwB,EAAE,EAAE;QACpE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,KAAK,CAAC,UAAU,CAAC,IAAA,2BAAQ,EAAC,GAAG,CAAC,CAAC,CAAA;YACjC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,8CAA8C,GAAG,CAAC,IAAI,GAAG,EAAG,CAAW,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;YACrG,CAAC;QACH,CAAC;QACD,MAAM,IAAA,iCAAc,EAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACpC,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,IAAA,iCAAa,EAA2B;QAC3D,mBAAmB,EAAE,QAAQ;QAC7B,KAAK;QACL,OAAO,EAAE;YACP,YAAY,EAAE,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,sBAAsB,EAAE,KAAK,CAAC,sBAAsB;YACpD;;;eAGG;YACH,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,+BAAuB;YAC7D,sBAAsB,EAAE,KAAK,CAAC,sBAAsB,IAAI,IAAI;YAC5D,MAAM,EAAE,KAAK,CAAC,MAAM;SACrB;QACD,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,aAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC;QAC3E,OAAO,EAAE,WAAW;QACpB,QAAQ,EAAE;YACR,UAAU,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;YACpC;;;eAGG;YACH,WAAW,EAAE,GAAG,EAAE,CAAC,KAAK;YACxB,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK;YACzB,gHAAgH;YAChH,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;gBACvC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAClC,IAAI,CAAC,IAAI;oBAAE,OAAO,SAAS,CAAA;gBAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAA;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;gBAC1B,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,CAAA;gBACnF,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAO,IAAI,CAAC,OAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;oBACzD,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,CAAA;oBAChF,OAAO,MAAM,CAAA;gBACf,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,MAAM,QAAQ,CAAC,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,EAAE,EAAE,OAAO,CAAC,CAAA;oBAClF,MAAM,CAAC,CAAA;gBACT,CAAC;YACH,CAAC;YACD,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,+CAA+C,CAAC,EAAE,CAAC;YAC1G,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI;YACxB,sHAAsH;YACtH,mBAAmB,EAAE,4BAAa;SACnC;QACD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,CAAC,EAAE,KAAK,CAAC,CAAC;KACX,CAAC,CAAA;IAEF,OAAO,EAAE,GAAG,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AAClE,CAAC","sourcesContent":["/*\n * A conversation for a product that has no editor of its own.\n *\n * ── What this is for ───────────────────────────────────────────────────────────\n * `board-ai` assembles its conversation from two sources: its own board editing tools, and\n * whatever other packages registered in the tool registry. The second half is the same work for\n * every product — read the specs, build the definitions, dispatch by name — but it only existed\n * inside the board assistant, so a product without a board had nowhere to ask a question from.\n * operato-figure hit exactly that: the dock was mounted, the tools were registered, and the only\n * mutation that could run them lived in a package figure does not load.\n *\n * ── Read only ──────────────────────────────────────────────────────────────────\n * A registry category may also carry `write` and `action` tools. Those produce edit ops for a\n * document the host owns, and a host with no document cannot apply them. Offering them here would\n * declare a door with no road behind it — the model would call one and the call would go nowhere.\n * `external` tools change something outside the conversation (board-import's\n * `materializeImportSession` creates a Board) and this door has no confirm-and-apply step, so they\n * are not offered either (A1, 2026-09-17). This runner offers, and dispatches, `read` only.\n *\n * The gate answers from the same list the definitions are built from, so what is offered and what\n * may be dispatched cannot drift apart.\n */\nimport type { AIClient, AIMessage } from '../types.js'\nimport { narrowToolScope, resolveToolScope, toolScopeGuidance, type ToolAccessCheck } from './tool-scope.js'\nimport { assistantChat, type AssistantChatResult } from './assistant-chat.js'\nimport { resultShapeOf } from './summarize.js'\nimport type { GroundingSources } from '../utils/index.js'\nimport type { ToolAttachment, ToolCallContext, UnattendedRunRef } from '../tool-registry/types.js'\nimport { defaultValueTranslate } from '../tool-registry/translate.js'\nimport { digestOf, recordToolCall, type ToolCallRecord, type ToolCallRun } from '../tool-registry/call-recorder.js'\n\n\n/** Lookups a registry conversation may take before it answers from what it has (operato-twin's assistant takes 16 too). */\nexport const REGISTRY_MAX_ITERATIONS = 16\n\nexport interface RegistryAssistantInput {\n /** Provider client. Must implement `chatWithTools`. */\n client: AIClient\n /** The conversation so far, newest last. The caller owns history and truncation. */\n messages: AIMessage[]\n /** Who the assistant is and what this surface is. The host owns this prose. */\n systemPrompt: string\n /**\n * Categories this conversation may use. **Omitting it allows nothing** — the same as an empty\n * list, a plain conversation with no tools (A1, 2026-09-17). It used to mean every registered\n * category, which is how a Board-creating tool reached any conversation.\n */\n toolCategories?: string[]\n /**\n * Answers whether the caller holds a tool's declared privilege. Without it, every tool that\n * declares a privilege stays closed. The host builds it from the request — this package does\n * not know auth.\n */\n toolAccess?: ToolAccessCheck\n /** Passed to tools as `context.host`. Client-supplied — never used for authorization. */\n hostContext?: any\n /**\n * Which conversation this is — `{ kind, target }` from the registered mode (ADR-0061 decision 3).\n *\n * Tools see it as `context.mode`. It is the server's value, resolved from the registry before the\n * turn runs, so a tool that behaves differently in two conversations reads it here and not from\n * `host`, which the client fills.\n */\n mode?: { kind: string; target?: string }\n /** Caller's koa state (domain, user, tx). Tools that read the database need it. */\n state: any\n model?: string\n temperature?: number\n maxTokens?: number\n /** Make the model call a tool before it may answer. For surfaces that report live state. */\n requireToolOnFirstTurn?: boolean\n /**\n * The request's translator — the stop notice is then in the reader's language, and tools write what a person\n * reads with it (`context.t`). Without it tools get `defaultValueTranslate`: the English default values.\n */\n t?: (key: string, params?: Record<string, any>) => string\n /**\n * The files sent with this turn, after the caller's server checked them — stored with this conversation, the\n * conversation the caller's to write, the mode's policy met (ADR-0061, 2026-10-16 decision 2). Tools see them as\n * `context.attachments`. Never built from the client's request: what a client puts in `hostContext` stays there.\n */\n attachments?: ToolAttachment[]\n /** Texts the answer is allowed to draw identifiers from, besides the tool results. */\n groundingSources?: Omit<GroundingSources, 'toolResults'>\n /** Stops the loop and the provider request - an unattended run's time limit. */\n signal?: AbortSignal\n /** Lookups before the answer; `REGISTRY_MAX_ITERATIONS` when absent. */\n maxIterations?: number\n /** At the lookup limit, one more call without tools to answer from what was read. On when absent. */\n salvageOnMaxIterations?: boolean\n /**\n * Each call as the recorders get it (`digestOf`, never its raw values) - an unattended run lists the reads its draft\n * rested on from the same digest the audit keeps, so the two cannot describe one call differently.\n */\n onToolCall?: (record: ToolCallRecord) => void\n /** Set for an unattended run; tools and recorders see it as `context.run`. */\n run?: UnattendedRunRef\n}\n\nexport interface RegistryAssistantResult extends AssistantChatResult<unknown> {\n /** Tool names actually offered to the model this turn. The caller may log or return it. */\n offeredTools: string[]\n}\n\n/**\n * Run one turn of a conversation whose only tools come from the registry.\n *\n * Throws when the client cannot use tools at all — a surface built on registered tools that\n * silently ran without them would answer from nothing and look like it had checked.\n */\nexport async function registryAssistantChat(input: RegistryAssistantInput): Promise<RegistryAssistantResult> {\n const { client, messages, systemPrompt, toolCategories, hostContext, state } = input\n\n if (!client.chatWithTools) {\n throw new Error('[registry-assistant] the configured AI client cannot call tools, so this conversation cannot run')\n }\n\n /*\n * Only `read` is offered here. `external` means the tool changes something outside the\n * conversation (board-import's `materializeImportSession` creates a Board); this door answers\n * questions and has no confirm-and-apply step, so a tool with an effect does not go through it\n * even when the caller holds the privilege.\n */\n const scope = narrowToolScope(\n await resolveToolScope(toolCategories, input.toolAccess),\n spec => spec.kind === 'read'\n )\n const specs = scope.specs\n const byName = new Map(specs.map(spec => [spec.name, spec]))\n const categoryOf = new Map(scope.categories.flatMap(category => category.specs.map(spec => [spec.name, category.name] as [string, string])))\n\n const tools = specs.map(spec => ({\n name: spec.name,\n description: spec.themeNote ? `${spec.description} ${spec.themeNote}` : spec.description,\n parameters: spec.schema\n }))\n\n /*\n * The rules for using a tool belong to whoever registered it. Collect only the exposed ones, so\n * a surface never reads instructions for a category it cannot call.\n */\n const guidance = toolScopeGuidance(scope)\n const prompt = guidance.length > 0 ? `${systemPrompt}\\n\\n${guidance.join('\\n\\n')}` : systemPrompt\n\n /*\n * `mode` 는 서버의 값이다 — 등록된 모드에서 왔고, `host` 와 달리 클라이언트가 못 고친다. A2 가 서기\n * 전에는 채울 것이 없어 비워 두었다(ADR-0061 결정 3 의 주석). 이제 부르는 쪽이 들고 있다.\n */\n const toolContext: ToolCallContext = {\n state,\n host: hostContext ?? {},\n ...(input.mode ? { mode: input.mode } : {}),\n /* 번역기와 첨부는 서버의 값이다 — `host` 에서 받지 않는다(ADR-0061 2026-10-16 판정 1·2). */\n t: input.t ?? defaultValueTranslate,\n attachments: input.attachments ?? [],\n ...(input.run ? { run: input.run } : {})\n }\n\n const recorded = async (run: ToolCallRun, context: ToolCallContext) => {\n if (input.onToolCall) {\n try {\n input.onToolCall(digestOf(run))\n } catch (e) {\n console.warn(`[registry-assistant] onToolCall failed for ${run.tool}:`, (e as Error)?.message ?? e)\n }\n }\n await recordToolCall(run, context)\n }\n\n const result = await assistantChat<ToolCallContext, unknown>({\n initialConversation: messages,\n tools,\n options: {\n systemPrompt: prompt,\n model: input.model,\n temperature: input.temperature,\n maxTokens: input.maxTokens,\n requireToolOnFirstTurn: input.requireToolOnFirstTurn,\n /*\n * A question across a product's records takes many lookups. At the limit the model answers from\n * what it read and says what is missing — the person is never told to split the question.\n */\n maxIterations: input.maxIterations ?? REGISTRY_MAX_ITERATIONS,\n salvageOnMaxIterations: input.salvageOnMaxIterations ?? true,\n signal: input.signal\n },\n chat: (msgs, toolDefs, opts) => client.chatWithTools!(msgs, toolDefs, opts),\n context: toolContext,\n dispatch: {\n isReadTool: name => byName.has(name),\n /*\n * This surface has no document, so nothing here can write. Saying so plainly makes the loop\n * report an unknown name back to the model instead of accepting a call and dropping it.\n */\n isWriteTool: () => false,\n isActionTool: () => false,\n /* Every call is handed to the recorders - an application's audit sees the tools other packages declared too. */\n executeReadTool: async (call, context) => {\n const spec = byName.get(call.name)\n if (!spec) return undefined\n const args = call.arguments ?? {}\n const started = Date.now()\n const record = { tool: spec.name, category: categoryOf.get(spec.name) ?? '', args }\n try {\n const result = await (spec.builder as any)(args, context)\n await recorded({ ...record, result, durationMs: Date.now() - started }, context)\n return result\n } catch (e) {\n await recorded({ ...record, error: e, durationMs: Date.now() - started }, context)\n throw e\n }\n },\n validateWriteToolCall: () => ({ valid: false, errors: ['this conversation has no document to write to'] }),\n toolCallToOp: () => null,\n /* The conversation row keeps the shape of each answer, as the audit does - never its values (ADR-0061 decision 8). */\n summarizeToolResult: resultShapeOf\n },\n groundingSources: input.groundingSources,\n t: input.t\n })\n\n return { ...result, offeredTools: tools.map(tool => tool.name) }\n}\n"]}
|
|
@@ -11,6 +11,7 @@ const tslib_1 = require("tslib");
|
|
|
11
11
|
const sdk_1 = tslib_1.__importDefault(require("@anthropic-ai/sdk"));
|
|
12
12
|
const factory_js_1 = require("../factory.js");
|
|
13
13
|
const parse_json_js_1 = require("../utils/parse-json.js");
|
|
14
|
+
const request_options_js_1 = require("./request-options.js");
|
|
14
15
|
const DEFAULT_MODEL = 'claude-sonnet-4-6';
|
|
15
16
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
16
17
|
class AnthropicAIClient {
|
|
@@ -43,7 +44,7 @@ class AnthropicAIClient {
|
|
|
43
44
|
...(system && { system }),
|
|
44
45
|
...(options.stopSequences && { stop_sequences: options.stopSequences }),
|
|
45
46
|
messages: conversation
|
|
46
|
-
});
|
|
47
|
+
}, (0, request_options_js_1.requestOptionsOf)(options));
|
|
47
48
|
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
48
49
|
return response;
|
|
49
50
|
}
|
|
@@ -90,7 +91,7 @@ class AnthropicAIClient {
|
|
|
90
91
|
messages: conversation,
|
|
91
92
|
tools: sdkTools,
|
|
92
93
|
...(toolChoice && { tool_choice: toolChoice })
|
|
93
|
-
});
|
|
94
|
+
}, (0, request_options_js_1.requestOptionsOf)(options));
|
|
94
95
|
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
95
96
|
const text = extractResponseText(response) || undefined;
|
|
96
97
|
const toolCalls = Array.isArray(response.content)
|
|
@@ -1 +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 return extractResponseText(await this.send(messages, options))\n }\n\n /** One request, the provider's response as it came: `generateJSON` reads whether it stopped at the output limit. */\n private async send(messages: AIMessage[], options: AIChatOptions): Promise<any> {\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 reportUsage(response, options, options.model ?? this.defaultModel)\n return 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 response = await this.send(messages, { ...options, systemPrompt: enforcedSystem })\n return parseJSONResponse<T>(extractResponseText(response), { cutShort: response?.stop_reason === 'max_tokens' })\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 reportUsage(response, options, options.model ?? this.defaultModel)\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 /** Every model Anthropic lists is a conversation model. */\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.sort()\n }\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.input_tokens,\n outputTokens: response?.usage?.output_tokens\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"]}
|
|
1
|
+
{"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../server/service/providers/anthropic.ts"],"names":[],"mappings":";;;;AAAA;;;;;GAKG;AACH,oEAAyC;AAczC,8CAAkD;AAClD,0DAA0D;AAC1D,6DAAuD;AAEvD,MAAM,aAAa,GAAG,mBAAmB,CAAA;AACzC,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B,MAAa,iBAAiB;IAK5B,YAAY,MAAsB;QAChC,IAAI,CAAC,GAAG,GAAG,IAAI,aAAS,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;SACnD,CAAC,CAAA;QACF,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,KAAK,IAAI,aAAa,CAAA;QACjD,IAAI,CAAC,EAAE,GAAG,aAAa,IAAI,CAAC,YAAY,EAAE,CAAA;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAqB,EAAE,UAAyB,EAAE;QAC3D,OAAO,mBAAmB,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,oHAAoH;IAC5G,KAAK,CAAC,IAAI,CAAC,QAAqB,EAAE,OAAsB;QAC9D,MAAM,kBAAkB,GAAG,QAAQ;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC;aACvB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,MAAM,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAEtF,MAAM,YAAY,GAAG,QAAQ;aAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC,CAAA;QAE1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;YACnD,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9E,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,QAAQ,EAAE,YAAY;SAChB,EAAE,IAAA,qCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QACpC,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAClE,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,MAA4B,EAC5B,UAAyB,EAAE;QAE3B,MAAM,QAAQ,GACZ,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QAE3E,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM;YAC/B,CAAC,CAAC,mDAAmD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACrF,CAAC,CAAC,EAAE,CAAA;QACN,MAAM,cAAc,GAClB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3D,iEAAiE;YACjE,UAAU,CAAA;QAEZ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAA;QACxF,OAAO,IAAA,iCAAiB,EAAI,mBAAmB,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,KAAK,YAAY,EAAE,CAAC,CAAA;IAClH,CAAC;IAED,KAAK,CAAC,MAAM,CACV,KAA4C,EAC5C,MAAc,EACd,UAA2B,EAAE;QAE7B,MAAM,SAAS,GACb,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK;YACzC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE;YAC5D,CAAC,CAAC;gBACE,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAA4B,EAAE,SAAS,EAAE,WAAW,EAAE;aACvF,CAAA;QAEP,OAAO,IAAI,CAAC,IAAI,CACd,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EACxE,OAAO,CACR,CAAA;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,QAAqB,EACrB,KAAe,EACf,UAA6B,EAAE;QAE/B,MAAM,kBAAkB,GAAG,QAAQ;aAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAChC,GAAG,CAAC,kBAAkB,CAAC;aACvB,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,MAAM,CAAC,CAAA;QACf,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACtF,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;QAEtF,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,YAAY,EAAE,CAAC,CAAC,UAAiB;SAClC,CAAC,CAAC,CAAA;QAEH,MAAM,UAAU,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAA;QAEpD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;YACnD,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9E,GAAG,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,CAAC;YACzB,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,QAAQ;YACf,GAAG,CAAC,UAAU,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;SACxC,EAAE,IAAA,qCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QACpC,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAElE,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAA;QACvD,MAAM,SAAS,GAAiB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC7D,CAAC,CAAC,QAAQ,CAAC,OAAO;iBACb,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC;iBAC9C,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC;gBAChB,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE;gBAC/F,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAwB;aAC1F,CAAC,CAAC;YACP,CAAC,CAAC,EAAE,CAAA;QAEN,OAAO;YACL,IAAI;YACJ,SAAS;YACT,UAAU,EAAE,4BAA4B,CAAC,QAAQ,CAAC,WAAW,CAAC;SAC/D,CAAA;IACH,CAAC;IAED,2DAA2D;IAC3D,KAAK,CAAC,UAAU;QACd,MAAM,GAAG,GAAa,EAAE,CAAA;QACxB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACpE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;IACnB,CAAC;CACF;AA1ID,8CA0IC;AAED,SAAS,WAAW,CAAC,QAAa,EAAE,OAAsB,EAAE,SAAiB;IAC3E,OAAO,CAAC,OAAO,EAAE,CAAC;QAChB,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,SAAS;QACnC,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY;QAC1C,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa;KAC7C,CAAC,CAAA;AACJ,CAAC;AAED,qEAAqE;AAErE,SAAS,kBAAkB,CAAC,GAAc;IACxC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACvD,OAAO,GAAG,CAAC,OAAO;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAoC,CAAC,IAAI,CAAC;SACpD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAc;IACxC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAA;IACjD,CAAC;IACD,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;KAClD,CAAA;AACH,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAmB;IAClD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAA;IAC1C,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAA;IAClF,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAChC,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,IAAI,CAAC,SAAS;YAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;SACxC,CAAA;IACH,CAAC;IACD,QAAQ;IACR,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,CAAA;IACzE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE;KACpE,CAAA;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,QAAa;IACxC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAA;IAC5D,OAAO,QAAQ,CAAC,OAAO;SACpB,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAC1C,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;SAC7B,IAAI,CAAC,EAAE,CAAC,CAAA;AACb,CAAC;AAED,SAAS,wBAAwB,CAAC,OAA0B;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,MAAM,CAAA;IAC3C,MAAM,eAAe,GAAG,OAAO,CAAC,sBAAsB,KAAK,KAAK,CAAA;IAChE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;IACzB,CAAC;IACD,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,OAAO;gBACL,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,OAAO,CAAC,SAAS;gBACvB,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;aAC5D,CAAA;QACH,CAAC;QACD,OAAO;YACL,IAAI,EAAE,KAAK;YACX,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;SAC5D,CAAA;IACH,CAAC;IACD,SAAS;IACT,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,GAAG,CAAC,eAAe,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC;KAC5D,CAAA;AACH,CAAC;AAED,SAAS,4BAA4B,CAAC,GAAQ;IAC5C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,UAAU;YACb,OAAO,UAAU,CAAA;QACnB,KAAK,YAAY;YACf,OAAO,YAAY,CAAA;QACrB,KAAK,eAAe;YAClB,OAAO,eAAe,CAAA;QACxB;YACE,OAAO,GAAG,IAAI,UAAU,CAAA;IAC5B,CAAC;AACH,CAAC;AAED,+BAA+B;AAC/B,IAAA,+BAAkB,EAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA","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'\nimport { requestOptionsOf } from './request-options.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 return extractResponseText(await this.send(messages, options))\n }\n\n /** One request, the provider's response as it came: `generateJSON` reads whether it stopped at the output limit. */\n private async send(messages: AIMessage[], options: AIChatOptions): Promise<any> {\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, requestOptionsOf(options))\n reportUsage(response, options, options.model ?? this.defaultModel)\n return 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 response = await this.send(messages, { ...options, systemPrompt: enforcedSystem })\n return parseJSONResponse<T>(extractResponseText(response), { cutShort: response?.stop_reason === 'max_tokens' })\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, requestOptionsOf(options))\n reportUsage(response, options, options.model ?? this.defaultModel)\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 /** Every model Anthropic lists is a conversation model. */\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.sort()\n }\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.input_tokens,\n outputTokens: response?.usage?.output_tokens\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"]}
|
|
@@ -13,6 +13,7 @@ exports.ProviderListError = exports.GeminiAIClient = void 0;
|
|
|
13
13
|
const generative_ai_1 = require("@google/generative-ai");
|
|
14
14
|
const factory_js_1 = require("../factory.js");
|
|
15
15
|
const parse_json_js_1 = require("../utils/parse-json.js");
|
|
16
|
+
const request_options_js_1 = require("./request-options.js");
|
|
16
17
|
// `gemini-3-pro` 는 2026-03-09 종료됨. 후속 권장 모델로 변경.
|
|
17
18
|
// 사용자가 자기 환경에서 활용 가능한 정확한 ID 는 ListModels 로 확인:
|
|
18
19
|
// curl "https://generativelanguage.googleapis.com/v1beta/models?key=$KEY"
|
|
@@ -29,7 +30,7 @@ class GeminiAIClient {
|
|
|
29
30
|
async chat(messages, options = {}) {
|
|
30
31
|
const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt);
|
|
31
32
|
const model = this.buildModel(options, systemPrompt);
|
|
32
|
-
const result = await model.generateContent({ contents });
|
|
33
|
+
const result = await model.generateContent({ contents }, (0, request_options_js_1.requestOptionsOf)(options));
|
|
33
34
|
reportUsage(result, options, options.model ?? this.defaultModel);
|
|
34
35
|
return result.response.text();
|
|
35
36
|
}
|
|
@@ -45,7 +46,7 @@ class GeminiAIClient {
|
|
|
45
46
|
...(systemPrompt && { systemInstruction: systemPrompt }),
|
|
46
47
|
generationConfig
|
|
47
48
|
}, this.requestOptions);
|
|
48
|
-
const result = await model.generateContent({ contents });
|
|
49
|
+
const result = await model.generateContent({ contents }, (0, request_options_js_1.requestOptionsOf)(options));
|
|
49
50
|
reportUsage(result, options, options.model ?? this.defaultModel);
|
|
50
51
|
const text = result.response.text();
|
|
51
52
|
return (0, parse_json_js_1.parseJSONResponse)(text, { cutShort: result.response.candidates?.[0]?.finishReason === 'MAX_TOKENS' });
|
|
@@ -83,7 +84,7 @@ class GeminiAIClient {
|
|
|
83
84
|
tools: [{ functionDeclarations }],
|
|
84
85
|
...(toolConfig && { toolConfig })
|
|
85
86
|
}, this.requestOptions);
|
|
86
|
-
const result = await model.generateContent({ contents });
|
|
87
|
+
const result = await model.generateContent({ contents }, (0, request_options_js_1.requestOptionsOf)(options));
|
|
87
88
|
reportUsage(result, options, options.model ?? this.defaultModel);
|
|
88
89
|
const response = result.response;
|
|
89
90
|
const candidate = response?.candidates?.[0];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../server/service/providers/gemini.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Google Gemini AI Client adapter.\n *\n * 기본 모델: gemini-3-pro (구성에서 override 가능).\n * SDK: @google/generative-ai\n *\n * 비고: Antigravity (Google 의 agentic IDE) 가 기본 백엔드로 사용하는 모델군.\n * 이 어댑터는 Gemini API 직접 호출 — Antigravity IDE 와는 별개.\n */\nimport { GoogleGenerativeAI } from '@google/generative-ai'\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\n// `gemini-3-pro` 는 2026-03-09 종료됨. 후속 권장 모델로 변경.\n// 사용자가 자기 환경에서 활용 가능한 정확한 ID 는 ListModels 로 확인:\n// curl \"https://generativelanguage.googleapis.com/v1beta/models?key=$KEY\"\nconst DEFAULT_MODEL = 'gemini-3-flash-preview'\nconst DEFAULT_MAX_TOKENS = 8192\n\nexport class GeminiAIClient implements AIClient {\n readonly id: string\n private sdk: GoogleGenerativeAI\n private defaultModel: string\n private apiKey: string\n private baseURL?: string\n\n constructor(config: AIClientConfig) {\n this.apiKey = config.apiKey\n this.baseURL = config.baseURL\n this.sdk = new GoogleGenerativeAI(config.apiKey)\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `gemini:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n const model = this.buildModel(options, systemPrompt)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n return result.response.text()\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 const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n const generationConfig: any = baseGenerationConfig(options)\n generationConfig.responseMimeType = 'application/json'\n if (options.schema) generationConfig.responseSchema = options.schema\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig\n }, this.requestOptions)\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const text = result.response.text()\n return parseJSONResponse<T>(text, { cutShort: result.response.candidates?.[0]?.finishReason === 'MAX_TOKENS' })\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 private buildModel(options: AIChatOptions, systemInstruction?: string) {\n return this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemInstruction && { systemInstruction }),\n generationConfig: baseGenerationConfig(options)\n }, this.requestOptions)\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n // Gemini 는 OpenAPI 3.0 subset — JSON Schema 의 일부 키를 거부함.\n // 'additionalProperties' / '$schema' / '$defs' 등을 제거.\n const functionDeclarations = tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeForGeminiSchema(t.parameters)\n }))\n\n const toolConfig = buildGeminiToolConfig(options)\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig: baseGenerationConfig(options),\n tools: [{ functionDeclarations }] as any,\n ...(toolConfig && { toolConfig })\n } as any, this.requestOptions)\n\n const result = await model.generateContent({ contents } as any)\n reportUsage(result, options, options.model ?? this.defaultModel)\n const response: any = result.response\n\n const candidate = response?.candidates?.[0]\n const parts = candidate?.content?.parts ?? []\n\n // Gemini 는 한 응답에서 (a) thinking part (p.thought === true) 와\n // (b) 본문 text part 를 함께 emit 할 수 있다. thought 는 사용자에게 노출하면\n // \"구조화 답변 + 산문 답변\" 형태의 중복으로 보이므로 제거.\n const textParts = parts\n .filter((p: any) => typeof p?.text === 'string' && p?.thought !== true)\n .map((p: any) => p.text)\n const text = textParts.join('') || undefined\n\n // Gemini 는 functionCall 에 자체 id 가 없음 — 어댑터가 turn 내부에서 부여.\n // id 포맷 'g:<name>:<seq>' — assistant 가 tool_result 를 만들 때 이 id 를 그대로\n // 넘기면 toGeminiPart 가 name 을 추출해 functionResponse.name 에 채운다.\n //\n // thoughtSignature: Gemini 2.5/3 부터 thinking 모델이 functionCall 에 서명을 붙임.\n // 다음 turn 에 그대로 echo 해야 400 에러 안 남 (cite: ai.google.dev/.../thought-signatures).\n let geminiCallSeq = 0\n const ts = Date.now()\n const toolCalls: AIToolCall[] = parts\n .filter((p: any) => p?.functionCall && typeof p.functionCall.name === 'string')\n .map((p: any) => {\n // SDK 가 camelCase 또는 snake_case 둘 중 하나로 emit — 둘 다 받음.\n const sig: string | undefined = p.thoughtSignature ?? p.thought_signature\n return {\n id: `g:${p.functionCall.name}:${ts}_${geminiCallSeq++}`,\n name: p.functionCall.name,\n arguments:\n p.functionCall.args && typeof p.functionCall.args === 'object'\n ? (p.functionCall.args as Record<string, any>)\n : {},\n ...(typeof sig === 'string' && sig.length > 0 && {\n providerMeta: { thoughtSignature: sig }\n })\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeGeminiStopReason(candidate?.finishReason, toolCalls.length > 0)\n }\n }\n\n /**\n * The SDK has no model list, so this reads the REST one. A conversation model is one that answers\n * generateContent; embedding and other models are left out.\n */\n async listModels(): Promise<string[]> {\n const base = (this.baseURL || 'https://generativelanguage.googleapis.com').replace(/\\/+$/, '')\n const ids: string[] = []\n let pageToken = ''\n do {\n const url = `${base}/v1beta/models?pageSize=1000${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`\n const response = await fetch(url, { headers: { 'x-goog-api-key': this.apiKey } })\n if (!response.ok) throw new ProviderListError(response.status, await response.text())\n const page: any = await response.json()\n for (const model of page.models ?? []) {\n if ((model.supportedGenerationMethods ?? []).includes('generateContent')) {\n ids.push(String(model.name).replace(/^models\\//, ''))\n }\n }\n pageToken = page.nextPageToken ?? ''\n } while (pageToken)\n return ids.sort()\n }\n\n private get requestOptions() {\n return this.baseURL ? { baseUrl: this.baseURL } : undefined\n }\n}\n\n/** A model list the provider refused, with the HTTP status the caller turns into a reason. */\nexport class ProviderListError extends Error {\n constructor(readonly status: number, body: string) {\n super(`model list failed with ${status}: ${body.slice(0, 200)}`)\n }\n}\n\nfunction reportUsage(result: any, options: AIChatOptions, requested: string): void {\n const usage = result?.response?.usageMetadata\n options.onUsage?.({\n model: result?.response?.modelVersion ?? requested,\n inputTokens: usage?.promptTokenCount,\n outputTokens: usage?.candidatesTokenCount\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction baseGenerationConfig(options: AIChatOptions): any {\n return {\n maxOutputTokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stopSequences: options.stopSequences })\n }\n}\n\nfunction splitMessages(\n messages: AIMessage[],\n externalSystem?: string\n): { systemPrompt: string; contents: any[] } {\n const systemMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n const conversation = messages.filter(m => m.role !== 'system')\n const systemPrompt = [externalSystem, ...systemMessages].filter(Boolean).join('\\n\\n')\n return { systemPrompt, contents: conversation.map(toGeminiContent) }\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 toGeminiContent(msg: AIMessage): any {\n // Our 'assistant' role → Gemini's 'model' role\n const role = msg.role === 'assistant' ? 'model' : msg.role\n const parts =\n typeof msg.content === 'string'\n ? [{ text: msg.content }]\n : msg.content.map(toGeminiPart)\n return { role, parts }\n}\n\nfunction toGeminiPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { text: part.text }\n }\n if (part.type === 'tool_use') {\n // Gemini: assistant 가 호출 의도 표현. functionCall.name + args 만 가짐 (id 없음).\n // thoughtSignature 는 thinking 모델 사용 시 필수 — providerMeta 에서 복원해 echo.\n const sig = part.providerMeta?.thoughtSignature\n return {\n functionCall: { name: part.name, args: part.arguments },\n ...(typeof sig === 'string' && sig.length > 0 && { thoughtSignature: sig })\n }\n }\n if (part.type === 'tool_result') {\n // Gemini: user role 에 functionResponse part 로 회신. 이름은 호출했던 tool name 과\n // 같아야 함 — assistant.ts 의 loop 에서 part 생성 시 함께 전달.\n let parsed: any = part.content\n try {\n parsed = JSON.parse(part.content)\n } catch {\n parsed = { result: part.content }\n }\n return {\n functionResponse: {\n // name 은 원래 호출했던 tool name. toolUseId 에 임베드되어 있지 않으므로\n // assistant 가 메시지 생성 시 별도 채워줘야 함 — 여기서는 placeholder.\n // 실제 사용 시 toolUseId 를 'tool_<name>_<seq>' 형태로 만들어 name 추출.\n name: extractGeminiNameFromId(part.toolUseId),\n response: parsed\n }\n }\n }\n // image\n if (part.source.kind === 'url') {\n // Gemini SDK 는 inlineData(base64) 만 지원. URL 은 호출 전에 fetch 필요.\n throw new Error(\n 'Gemini adapter: image URL is not supported directly. Fetch the URL and pass as Buffer.'\n )\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 inlineData: { data, mimeType: part.source.mediaType }\n }\n}\n\n/**\n * Gemini 의 functionCall 에는 자체 id 가 없어서 chatWithTools 가 'gemini_<ts>_<seq>'\n * 형태의 가짜 id 를 만든다. 그런데 functionResponse 에는 name 이 필수 — assistant 의\n * tool 실행기는 이 id 와 name 을 별도 매핑해야 한다. assistant 가 tool_result part 를\n * 만들 때 toolUseId 에 'name:<actualName>;id:<originalId>' 같은 인코딩을 할 수 있지만\n * 그건 invasive — 대신 toolUseId 가 'g:<name>:<id>' 형태면 name 을 직접 추출.\n *\n * 현재는 fallback: id 가 'g:' 로 시작하면 두 번째 segment 를 name 으로,\n * 아니면 그대로 사용 (Gemini 가 받지 않으면 에러 — assistant 가 책임지고 인코딩).\n */\nfunction extractGeminiNameFromId(id: string): string {\n if (id.startsWith('g:')) {\n const parts = id.split(':')\n return parts[1] || id\n }\n return id\n}\n\n/**\n * JSON Schema → Gemini OpenAPI 3.0 subset 변환.\n * Gemini API 가 거부하는 키들 (deep) 제거.\n * 보존: type / properties / items / required / enum / description / format / nullable\n */\nconst GEMINI_REJECTED_KEYS = new Set([\n 'additionalProperties',\n '$schema',\n '$defs',\n '$ref',\n '$id',\n 'definitions',\n 'patternProperties',\n 'dependencies'\n])\n\nfunction sanitizeForGeminiSchema(schema: any): any {\n if (schema === null || schema === undefined) return schema\n if (Array.isArray(schema)) return schema.map(sanitizeForGeminiSchema)\n if (typeof schema !== 'object') return schema\n const out: any = {}\n for (const [k, v] of Object.entries(schema)) {\n if (GEMINI_REJECTED_KEYS.has(k)) continue\n out[k] = sanitizeForGeminiSchema(v)\n }\n return out\n}\n\nfunction buildGeminiToolConfig(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') {\n return { functionCallingConfig: { mode: 'NONE' } }\n }\n if (choice === 'required') {\n return {\n functionCallingConfig: {\n mode: 'ANY',\n ...(options.forceTool && { allowedFunctionNames: [options.forceTool] })\n }\n }\n }\n return { functionCallingConfig: { mode: 'AUTO' } }\n}\n\nfunction normalizeGeminiStopReason(\n raw: any,\n hasToolCalls: boolean\n): AIToolChatResult['stopReason'] {\n // Gemini 는 function call 발생 시 finishReason 을 'STOP' 으로 마킹할 수 있어\n // toolCalls 존재 여부로 우선 판정.\n if (hasToolCalls) return 'tool_use'\n switch (raw) {\n case 'STOP':\n return 'end_turn'\n case 'MAX_TOKENS':\n return 'max_tokens'\n case 'SAFETY':\n case 'RECITATION':\n case 'OTHER':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('gemini', config => new GeminiAIClient(config))\n"]}
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../server/service/providers/gemini.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,yDAA0D;AAc1D,8CAAkD;AAClD,0DAA0D;AAC1D,6DAAuD;AAEvD,iDAAiD;AACjD,gDAAgD;AAChD,4EAA4E;AAC5E,MAAM,aAAa,GAAG,wBAAwB,CAAA;AAC9C,MAAM,kBAAkB,GAAG,IAAI,CAAA;AAE/B,MAAa,cAAc;IAOzB,YAAY,MAAsB;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;QAC3B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAA;QAC7B,IAAI,CAAC,GAAG,GAAG,IAAI,kCAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,KAAK,IAAI,aAAa,CAAA;QACjD,IAAI,CAAC,EAAE,GAAG,UAAU,IAAI,CAAC,YAAY,EAAE,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAqB,EAAE,UAAyB,EAAE;QAC3D,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;QAChF,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAS,EAAE,IAAA,qCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QAC1F,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAChE,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,YAAY,CAChB,MAA4B,EAC5B,UAAyB,EAAE;QAE3B,MAAM,QAAQ,GACZ,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QAC3E,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;QAEhF,MAAM,gBAAgB,GAAQ,oBAAoB,CAAC,OAAO,CAAC,CAAA;QAC3D,gBAAgB,CAAC,gBAAgB,GAAG,kBAAkB,CAAA;QACtD,IAAI,OAAO,CAAC,MAAM;YAAE,gBAAgB,CAAC,cAAc,GAAG,OAAO,CAAC,MAAM,CAAA;QAEpE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACxC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,GAAG,CAAC,YAAY,IAAI,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;YACxD,gBAAgB;SACjB,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;QACvB,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAS,EAAE,IAAA,qCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QAC1F,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAChE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACnC,OAAO,IAAA,iCAAiB,EAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,YAAY,KAAK,YAAY,EAAE,CAAC,CAAA;IACjH,CAAC;IAED,KAAK,CAAC,MAAM,CACV,KAA4C,EAC5C,MAAc,EACd,UAA2B,EAAE;QAE7B,MAAM,SAAS,GACb,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK;YACzC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,EAAE;YAC5D,CAAC,CAAC;gBACE,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAA4B,EAAE,SAAS,EAAE,WAAW,EAAE;aACvF,CAAA;QAEP,OAAO,IAAI,CAAC,IAAI,CACd,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EACxE,OAAO,CACR,CAAA;IACH,CAAC;IAEO,UAAU,CAAC,OAAsB,EAAE,iBAA0B;QACnE,OAAO,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACjC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,GAAG,CAAC,iBAAiB,IAAI,EAAE,iBAAiB,EAAE,CAAC;YAC/C,gBAAgB,EAAE,oBAAoB,CAAC,OAAO,CAAC;SAChD,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,aAAa,CACjB,QAAqB,EACrB,KAAe,EACf,UAA6B,EAAE;QAE/B,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;QAEhF,yDAAyD;QACzD,sDAAsD;QACtD,MAAM,oBAAoB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC3C,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,UAAU,EAAE,uBAAuB,CAAC,CAAC,CAAC,UAAU,CAAC;SAClD,CAAC,CAAC,CAAA;QAEH,MAAM,UAAU,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAA;QAEjD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACxC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY;YACzC,GAAG,CAAC,YAAY,IAAI,EAAE,iBAAiB,EAAE,YAAY,EAAE,CAAC;YACxD,gBAAgB,EAAE,oBAAoB,CAAC,OAAO,CAAC;YAC/C,KAAK,EAAE,CAAC,EAAE,oBAAoB,EAAE,CAAQ;YACxC,GAAG,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,CAAC;SAC3B,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;QAE9B,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAS,EAAE,IAAA,qCAAgB,EAAC,OAAO,CAAC,CAAC,CAAA;QAC1F,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,CAAA;QAChE,MAAM,QAAQ,GAAQ,MAAM,CAAC,QAAQ,CAAA;QAErC,MAAM,SAAS,GAAG,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAA;QAC3C,MAAM,KAAK,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAA;QAE7C,2DAA2D;QAC3D,0DAA0D;QAC1D,qCAAqC;QACrC,MAAM,SAAS,GAAG,KAAK;aACpB,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;aACtE,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC1B,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,SAAS,CAAA;QAE5C,0DAA0D;QAC1D,qEAAqE;QACrE,6DAA6D;QAC7D,EAAE;QACF,wEAAwE;QACxE,iFAAiF;QACjF,IAAI,aAAa,GAAG,CAAC,CAAA;QACrB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACrB,MAAM,SAAS,GAAiB,KAAK;aAClC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,YAAY,IAAI,OAAO,CAAC,CAAC,YAAY,CAAC,IAAI,KAAK,QAAQ,CAAC;aAC9E,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;YACd,uDAAuD;YACvD,MAAM,GAAG,GAAuB,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,iBAAiB,CAAA;YACzE,OAAO;gBACL,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,IAAI,EAAE,IAAI,aAAa,EAAE,EAAE;gBACvD,IAAI,EAAE,CAAC,CAAC,YAAY,CAAC,IAAI;gBACzB,SAAS,EACP,CAAC,CAAC,YAAY,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,YAAY,CAAC,IAAI,KAAK,QAAQ;oBAC5D,CAAC,CAAE,CAAC,CAAC,YAAY,CAAC,IAA4B;oBAC9C,CAAC,CAAC,EAAE;gBACR,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI;oBAC/C,YAAY,EAAE,EAAE,gBAAgB,EAAE,GAAG,EAAE;iBACxC,CAAC;aACH,CAAA;QACH,CAAC,CAAC,CAAA;QAEJ,OAAO;YACL,IAAI;YACJ,SAAS;YACT,UAAU,EAAE,yBAAyB,CAAC,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;SACrF,CAAA;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACd,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,2CAA2C,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAC9F,MAAM,GAAG,GAAa,EAAE,CAAA;QACxB,IAAI,SAAS,GAAG,EAAE,CAAA;QAClB,GAAG,CAAC;YACF,MAAM,GAAG,GAAG,GAAG,IAAI,+BAA+B,SAAS,CAAC,CAAC,CAAC,cAAc,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;YAClH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,gBAAgB,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACjF,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;YACrF,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YACvC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBACtC,IAAI,CAAC,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBACzE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAA;gBACvD,CAAC;YACH,CAAC;YACD,SAAS,GAAG,IAAI,CAAC,aAAa,IAAI,EAAE,CAAA;QACtC,CAAC,QAAQ,SAAS,EAAC;QACnB,OAAO,GAAG,CAAC,IAAI,EAAE,CAAA;IACnB,CAAC;IAED,IAAY,cAAc;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAC7D,CAAC;CACF;AA5KD,wCA4KC;AAED,8FAA8F;AAC9F,MAAa,iBAAkB,SAAQ,KAAK;IAC1C,YAAqB,MAAc,EAAE,IAAY;QAC/C,KAAK,CAAC,0BAA0B,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QAD7C,WAAM,GAAN,MAAM,CAAQ;IAEnC,CAAC;CACF;AAJD,8CAIC;AAED,SAAS,WAAW,CAAC,MAAW,EAAE,OAAsB,EAAE,SAAiB;IACzE,MAAM,KAAK,GAAG,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAA;IAC7C,OAAO,CAAC,OAAO,EAAE,CAAC;QAChB,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,IAAI,SAAS;QAClD,WAAW,EAAE,KAAK,EAAE,gBAAgB;QACpC,YAAY,EAAE,KAAK,EAAE,oBAAoB;KAC1C,CAAC,CAAA;AACJ,CAAC;AAED,qEAAqE;AAErE,SAAS,oBAAoB,CAAC,OAAsB;IAClD,OAAO;QACL,eAAe,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB;QACxD,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;QAC9E,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;KACvE,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CACpB,QAAqB,EACrB,cAAuB;IAEvB,MAAM,cAAc,GAAG,QAAQ;SAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;SAChC,GAAG,CAAC,kBAAkB,CAAC;SACvB,MAAM,CAAC,OAAO,CAAC,CAAA;IAClB,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAA;IAC9D,MAAM,YAAY,GAAG,CAAC,cAAc,EAAE,GAAG,cAAc,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACrF,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAA;AACtE,CAAC;AAED,SAAS,kBAAkB,CAAC,GAAc;IACxC,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC,OAAO,CAAA;IACvD,OAAO,GAAG,CAAC,OAAO;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAC9B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAE,CAAoC,CAAC,IAAI,CAAC;SACpD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,SAAS,eAAe,CAAC,GAAc;IACrC,+CAA+C;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAA;IAC1D,MAAM,KAAK,GACT,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;QAC7B,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACnC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,IAAmB;IACvC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAA;IAC5B,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,uEAAuE;QACvE,qEAAqE;QACrE,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,gBAAgB,CAAA;QAC/C,OAAO;YACL,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE;YACvD,GAAG,CAAC,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;SAC5E,CAAA;IACH,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAChC,uEAAuE;QACvE,kDAAkD;QAClD,IAAI,MAAM,GAAQ,IAAI,CAAC,OAAO,CAAA;QAC9B,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,CAAA;QACnC,CAAC;QACD,OAAO;YACL,gBAAgB,EAAE;gBAChB,sDAAsD;gBACtD,qDAAqD;gBACrD,2DAA2D;gBAC3D,IAAI,EAAE,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC7C,QAAQ,EAAE,MAAM;aACjB;SACF,CAAA;IACH,CAAC;IACD,QAAQ;IACR,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC/B,8DAA8D;QAC9D,MAAM,IAAI,KAAK,CACb,wFAAwF,CACzF,CAAA;IACH,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpD,OAAO;QACL,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE;KACtD,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,uBAAuB,CAAC,EAAU;IACzC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC3B,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACvB,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,sBAAsB;IACtB,SAAS;IACT,OAAO;IACP,MAAM;IACN,KAAK;IACL,aAAa;IACb,mBAAmB;IACnB,cAAc;CACf,CAAC,CAAA;AAEF,SAAS,uBAAuB,CAAC,MAAW;IAC1C,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAA;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAA;IACrE,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAA;IAC7C,MAAM,GAAG,GAAQ,EAAE,CAAA;IACnB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAQ;QACzC,GAAG,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,qBAAqB,CAAC,OAA0B;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,IAAI,MAAM,CAAA;IAC3C,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;IACpD,CAAC;IACD,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,OAAO;YACL,qBAAqB,EAAE;gBACrB,IAAI,EAAE,KAAK;gBACX,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE,oBAAoB,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;aACxE;SACF,CAAA;IACH,CAAC;IACD,OAAO,EAAE,qBAAqB,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;AACpD,CAAC;AAED,SAAS,yBAAyB,CAChC,GAAQ,EACR,YAAqB;IAErB,gEAAgE;IAChE,0BAA0B;IAC1B,IAAI,YAAY;QAAE,OAAO,UAAU,CAAA;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,UAAU,CAAA;QACnB,KAAK,YAAY;YACf,OAAO,YAAY,CAAA;QACrB,KAAK,QAAQ,CAAC;QACd,KAAK,YAAY,CAAC;QAClB,KAAK,OAAO;YACV,OAAO,eAAe,CAAA;QACxB;YACE,OAAO,GAAG,IAAI,UAAU,CAAA;IAC5B,CAAC;AACH,CAAC;AAED,+BAA+B;AAC/B,IAAA,+BAAkB,EAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA","sourcesContent":["/**\n * Google Gemini AI Client adapter.\n *\n * 기본 모델: gemini-3-pro (구성에서 override 가능).\n * SDK: @google/generative-ai\n *\n * 비고: Antigravity (Google 의 agentic IDE) 가 기본 백엔드로 사용하는 모델군.\n * 이 어댑터는 Gemini API 직접 호출 — Antigravity IDE 와는 별개.\n */\nimport { GoogleGenerativeAI } from '@google/generative-ai'\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'\nimport { requestOptionsOf } from './request-options.js'\n\n// `gemini-3-pro` 는 2026-03-09 종료됨. 후속 권장 모델로 변경.\n// 사용자가 자기 환경에서 활용 가능한 정확한 ID 는 ListModels 로 확인:\n// curl \"https://generativelanguage.googleapis.com/v1beta/models?key=$KEY\"\nconst DEFAULT_MODEL = 'gemini-3-flash-preview'\nconst DEFAULT_MAX_TOKENS = 8192\n\nexport class GeminiAIClient implements AIClient {\n readonly id: string\n private sdk: GoogleGenerativeAI\n private defaultModel: string\n private apiKey: string\n private baseURL?: string\n\n constructor(config: AIClientConfig) {\n this.apiKey = config.apiKey\n this.baseURL = config.baseURL\n this.sdk = new GoogleGenerativeAI(config.apiKey)\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `gemini:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n const model = this.buildModel(options, systemPrompt)\n const result = await model.generateContent({ contents } as any, requestOptionsOf(options))\n reportUsage(result, options, options.model ?? this.defaultModel)\n return result.response.text()\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 const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n const generationConfig: any = baseGenerationConfig(options)\n generationConfig.responseMimeType = 'application/json'\n if (options.schema) generationConfig.responseSchema = options.schema\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig\n }, this.requestOptions)\n const result = await model.generateContent({ contents } as any, requestOptionsOf(options))\n reportUsage(result, options, options.model ?? this.defaultModel)\n const text = result.response.text()\n return parseJSONResponse<T>(text, { cutShort: result.response.candidates?.[0]?.finishReason === 'MAX_TOKENS' })\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 private buildModel(options: AIChatOptions, systemInstruction?: string) {\n return this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemInstruction && { systemInstruction }),\n generationConfig: baseGenerationConfig(options)\n }, this.requestOptions)\n }\n\n async chatWithTools(\n messages: AIMessage[],\n tools: AITool[],\n options: AIToolChatOptions = {}\n ): Promise<AIToolChatResult> {\n const { systemPrompt, contents } = splitMessages(messages, options.systemPrompt)\n\n // Gemini 는 OpenAPI 3.0 subset — JSON Schema 의 일부 키를 거부함.\n // 'additionalProperties' / '$schema' / '$defs' 등을 제거.\n const functionDeclarations = tools.map(t => ({\n name: t.name,\n description: t.description,\n parameters: sanitizeForGeminiSchema(t.parameters)\n }))\n\n const toolConfig = buildGeminiToolConfig(options)\n\n const model = this.sdk.getGenerativeModel({\n model: options.model ?? this.defaultModel,\n ...(systemPrompt && { systemInstruction: systemPrompt }),\n generationConfig: baseGenerationConfig(options),\n tools: [{ functionDeclarations }] as any,\n ...(toolConfig && { toolConfig })\n } as any, this.requestOptions)\n\n const result = await model.generateContent({ contents } as any, requestOptionsOf(options))\n reportUsage(result, options, options.model ?? this.defaultModel)\n const response: any = result.response\n\n const candidate = response?.candidates?.[0]\n const parts = candidate?.content?.parts ?? []\n\n // Gemini 는 한 응답에서 (a) thinking part (p.thought === true) 와\n // (b) 본문 text part 를 함께 emit 할 수 있다. thought 는 사용자에게 노출하면\n // \"구조화 답변 + 산문 답변\" 형태의 중복으로 보이므로 제거.\n const textParts = parts\n .filter((p: any) => typeof p?.text === 'string' && p?.thought !== true)\n .map((p: any) => p.text)\n const text = textParts.join('') || undefined\n\n // Gemini 는 functionCall 에 자체 id 가 없음 — 어댑터가 turn 내부에서 부여.\n // id 포맷 'g:<name>:<seq>' — assistant 가 tool_result 를 만들 때 이 id 를 그대로\n // 넘기면 toGeminiPart 가 name 을 추출해 functionResponse.name 에 채운다.\n //\n // thoughtSignature: Gemini 2.5/3 부터 thinking 모델이 functionCall 에 서명을 붙임.\n // 다음 turn 에 그대로 echo 해야 400 에러 안 남 (cite: ai.google.dev/.../thought-signatures).\n let geminiCallSeq = 0\n const ts = Date.now()\n const toolCalls: AIToolCall[] = parts\n .filter((p: any) => p?.functionCall && typeof p.functionCall.name === 'string')\n .map((p: any) => {\n // SDK 가 camelCase 또는 snake_case 둘 중 하나로 emit — 둘 다 받음.\n const sig: string | undefined = p.thoughtSignature ?? p.thought_signature\n return {\n id: `g:${p.functionCall.name}:${ts}_${geminiCallSeq++}`,\n name: p.functionCall.name,\n arguments:\n p.functionCall.args && typeof p.functionCall.args === 'object'\n ? (p.functionCall.args as Record<string, any>)\n : {},\n ...(typeof sig === 'string' && sig.length > 0 && {\n providerMeta: { thoughtSignature: sig }\n })\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeGeminiStopReason(candidate?.finishReason, toolCalls.length > 0)\n }\n }\n\n /**\n * The SDK has no model list, so this reads the REST one. A conversation model is one that answers\n * generateContent; embedding and other models are left out.\n */\n async listModels(): Promise<string[]> {\n const base = (this.baseURL || 'https://generativelanguage.googleapis.com').replace(/\\/+$/, '')\n const ids: string[] = []\n let pageToken = ''\n do {\n const url = `${base}/v1beta/models?pageSize=1000${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`\n const response = await fetch(url, { headers: { 'x-goog-api-key': this.apiKey } })\n if (!response.ok) throw new ProviderListError(response.status, await response.text())\n const page: any = await response.json()\n for (const model of page.models ?? []) {\n if ((model.supportedGenerationMethods ?? []).includes('generateContent')) {\n ids.push(String(model.name).replace(/^models\\//, ''))\n }\n }\n pageToken = page.nextPageToken ?? ''\n } while (pageToken)\n return ids.sort()\n }\n\n private get requestOptions() {\n return this.baseURL ? { baseUrl: this.baseURL } : undefined\n }\n}\n\n/** A model list the provider refused, with the HTTP status the caller turns into a reason. */\nexport class ProviderListError extends Error {\n constructor(readonly status: number, body: string) {\n super(`model list failed with ${status}: ${body.slice(0, 200)}`)\n }\n}\n\nfunction reportUsage(result: any, options: AIChatOptions, requested: string): void {\n const usage = result?.response?.usageMetadata\n options.onUsage?.({\n model: result?.response?.modelVersion ?? requested,\n inputTokens: usage?.promptTokenCount,\n outputTokens: usage?.candidatesTokenCount\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction baseGenerationConfig(options: AIChatOptions): any {\n return {\n maxOutputTokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stopSequences: options.stopSequences })\n }\n}\n\nfunction splitMessages(\n messages: AIMessage[],\n externalSystem?: string\n): { systemPrompt: string; contents: any[] } {\n const systemMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n const conversation = messages.filter(m => m.role !== 'system')\n const systemPrompt = [externalSystem, ...systemMessages].filter(Boolean).join('\\n\\n')\n return { systemPrompt, contents: conversation.map(toGeminiContent) }\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 toGeminiContent(msg: AIMessage): any {\n // Our 'assistant' role → Gemini's 'model' role\n const role = msg.role === 'assistant' ? 'model' : msg.role\n const parts =\n typeof msg.content === 'string'\n ? [{ text: msg.content }]\n : msg.content.map(toGeminiPart)\n return { role, parts }\n}\n\nfunction toGeminiPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { text: part.text }\n }\n if (part.type === 'tool_use') {\n // Gemini: assistant 가 호출 의도 표현. functionCall.name + args 만 가짐 (id 없음).\n // thoughtSignature 는 thinking 모델 사용 시 필수 — providerMeta 에서 복원해 echo.\n const sig = part.providerMeta?.thoughtSignature\n return {\n functionCall: { name: part.name, args: part.arguments },\n ...(typeof sig === 'string' && sig.length > 0 && { thoughtSignature: sig })\n }\n }\n if (part.type === 'tool_result') {\n // Gemini: user role 에 functionResponse part 로 회신. 이름은 호출했던 tool name 과\n // 같아야 함 — assistant.ts 의 loop 에서 part 생성 시 함께 전달.\n let parsed: any = part.content\n try {\n parsed = JSON.parse(part.content)\n } catch {\n parsed = { result: part.content }\n }\n return {\n functionResponse: {\n // name 은 원래 호출했던 tool name. toolUseId 에 임베드되어 있지 않으므로\n // assistant 가 메시지 생성 시 별도 채워줘야 함 — 여기서는 placeholder.\n // 실제 사용 시 toolUseId 를 'tool_<name>_<seq>' 형태로 만들어 name 추출.\n name: extractGeminiNameFromId(part.toolUseId),\n response: parsed\n }\n }\n }\n // image\n if (part.source.kind === 'url') {\n // Gemini SDK 는 inlineData(base64) 만 지원. URL 은 호출 전에 fetch 필요.\n throw new Error(\n 'Gemini adapter: image URL is not supported directly. Fetch the URL and pass as Buffer.'\n )\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 inlineData: { data, mimeType: part.source.mediaType }\n }\n}\n\n/**\n * Gemini 의 functionCall 에는 자체 id 가 없어서 chatWithTools 가 'gemini_<ts>_<seq>'\n * 형태의 가짜 id 를 만든다. 그런데 functionResponse 에는 name 이 필수 — assistant 의\n * tool 실행기는 이 id 와 name 을 별도 매핑해야 한다. assistant 가 tool_result part 를\n * 만들 때 toolUseId 에 'name:<actualName>;id:<originalId>' 같은 인코딩을 할 수 있지만\n * 그건 invasive — 대신 toolUseId 가 'g:<name>:<id>' 형태면 name 을 직접 추출.\n *\n * 현재는 fallback: id 가 'g:' 로 시작하면 두 번째 segment 를 name 으로,\n * 아니면 그대로 사용 (Gemini 가 받지 않으면 에러 — assistant 가 책임지고 인코딩).\n */\nfunction extractGeminiNameFromId(id: string): string {\n if (id.startsWith('g:')) {\n const parts = id.split(':')\n return parts[1] || id\n }\n return id\n}\n\n/**\n * JSON Schema → Gemini OpenAPI 3.0 subset 변환.\n * Gemini API 가 거부하는 키들 (deep) 제거.\n * 보존: type / properties / items / required / enum / description / format / nullable\n */\nconst GEMINI_REJECTED_KEYS = new Set([\n 'additionalProperties',\n '$schema',\n '$defs',\n '$ref',\n '$id',\n 'definitions',\n 'patternProperties',\n 'dependencies'\n])\n\nfunction sanitizeForGeminiSchema(schema: any): any {\n if (schema === null || schema === undefined) return schema\n if (Array.isArray(schema)) return schema.map(sanitizeForGeminiSchema)\n if (typeof schema !== 'object') return schema\n const out: any = {}\n for (const [k, v] of Object.entries(schema)) {\n if (GEMINI_REJECTED_KEYS.has(k)) continue\n out[k] = sanitizeForGeminiSchema(v)\n }\n return out\n}\n\nfunction buildGeminiToolConfig(options: AIToolChatOptions): any | undefined {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') {\n return { functionCallingConfig: { mode: 'NONE' } }\n }\n if (choice === 'required') {\n return {\n functionCallingConfig: {\n mode: 'ANY',\n ...(options.forceTool && { allowedFunctionNames: [options.forceTool] })\n }\n }\n }\n return { functionCallingConfig: { mode: 'AUTO' } }\n}\n\nfunction normalizeGeminiStopReason(\n raw: any,\n hasToolCalls: boolean\n): AIToolChatResult['stopReason'] {\n // Gemini 는 function call 발생 시 finishReason 을 'STOP' 으로 마킹할 수 있어\n // toolCalls 존재 여부로 우선 판정.\n if (hasToolCalls) return 'tool_use'\n switch (raw) {\n case 'STOP':\n return 'end_turn'\n case 'MAX_TOKENS':\n return 'max_tokens'\n case 'SAFETY':\n case 'RECITATION':\n case 'OTHER':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('gemini', config => new GeminiAIClient(config))\n"]}
|
|
@@ -15,6 +15,7 @@ const tslib_1 = require("tslib");
|
|
|
15
15
|
const openai_1 = tslib_1.__importDefault(require("openai"));
|
|
16
16
|
const factory_js_1 = require("../factory.js");
|
|
17
17
|
const parse_json_js_1 = require("../utils/parse-json.js");
|
|
18
|
+
const request_options_js_1 = require("./request-options.js");
|
|
18
19
|
const DEFAULT_MODEL = 'gpt-4o';
|
|
19
20
|
const DEFAULT_MAX_TOKENS = 4096;
|
|
20
21
|
class OpenAIAIClient {
|
|
@@ -36,7 +37,7 @@ class OpenAIAIClient {
|
|
|
36
37
|
...(options.temperature !== undefined && { temperature: options.temperature }),
|
|
37
38
|
...(options.stopSequences && { stop: options.stopSequences }),
|
|
38
39
|
messages: sdkMessages
|
|
39
|
-
});
|
|
40
|
+
}, (0, request_options_js_1.requestOptionsOf)(options));
|
|
40
41
|
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
41
42
|
return response.choices[0]?.message?.content ?? '';
|
|
42
43
|
}
|
|
@@ -56,7 +57,7 @@ class OpenAIAIClient {
|
|
|
56
57
|
...(options.temperature !== undefined && { temperature: options.temperature }),
|
|
57
58
|
response_format: { type: 'json_object' },
|
|
58
59
|
messages: sdkMessages
|
|
59
|
-
});
|
|
60
|
+
}, (0, request_options_js_1.requestOptionsOf)(options));
|
|
60
61
|
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
61
62
|
const text = response.choices[0]?.message?.content ?? '{}';
|
|
62
63
|
return (0, parse_json_js_1.parseJSONResponse)(text, { cutShort: response.choices[0]?.finish_reason === 'length' });
|
|
@@ -91,7 +92,7 @@ class OpenAIAIClient {
|
|
|
91
92
|
tools: sdkTools,
|
|
92
93
|
...(toolChoice !== undefined && { tool_choice: toolChoice }),
|
|
93
94
|
...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })
|
|
94
|
-
});
|
|
95
|
+
}, (0, request_options_js_1.requestOptionsOf)(options));
|
|
95
96
|
reportUsage(response, options, options.model ?? this.defaultModel);
|
|
96
97
|
const choice = response.choices?.[0];
|
|
97
98
|
const message = choice?.message;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../server/service/providers/openai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI AI Client adapter.\n *\n * 기본 모델: gpt-4o (구성에서 override 가능).\n * SDK: openai\n *\n * organization 필드 지원.\n */\nimport OpenAI from 'openai'\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 = 'gpt-4o'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class OpenAIAIClient implements AIClient {\n readonly id: string\n private sdk: OpenAI\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new OpenAI({\n apiKey: config.apiKey,\n ...(config.organization && { organization: config.organization }),\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `openai:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n return response.choices[0]?.message?.content ?? ''\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 sdkMessages = buildSDKMessages(messages, enforcedSystem)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n response_format: { type: 'json_object' },\n messages: sdkMessages as any\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n const text = response.choices[0]?.message?.content ?? '{}'\n return parseJSONResponse<T>(text, { cutShort: response.choices[0]?.finish_reason === 'length' })\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 sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n\n const sdkTools = tools.map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters\n }\n }))\n\n const toolChoice = buildOpenAIToolChoice(options)\n\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any,\n tools: sdkTools,\n ...(toolChoice !== undefined && { tool_choice: toolChoice as any }),\n ...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })\n })\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const choice = response.choices?.[0]\n const message = choice?.message\n const text = message?.content || undefined\n\n const toolCalls: AIToolCall[] = (message?.tool_calls || []).map((tc: any) => {\n const fn = tc.function\n let args: Record<string, any> = {}\n try {\n args = fn?.arguments ? JSON.parse(fn.arguments) : {}\n } catch {\n args = {}\n }\n return {\n id: typeof tc.id === 'string' && tc.id ? tc.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: fn?.name ?? '',\n arguments: args\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeOpenAIStopReason(choice?.finish_reason)\n }\n }\n\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.filter(isOpenAIChatModel).sort()\n }\n}\n\n/**\n * OpenAI's model list has no capability field, so the conversation models are told apart by name: the GPT and\n * o-series families, without their audio, realtime, speech, image, search and instruct variants. One rule, kept by a test.\n */\nexport function isOpenAIChatModel(id: string): boolean {\n if (!/^(gpt-|chatgpt-|o\\d)/.test(id)) return false\n return !/(audio|realtime|tts|transcribe|image|search|instruct|embedding|moderation)/.test(id)\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.prompt_tokens,\n outputTokens: response?.usage?.completion_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction buildSDKMessages(messages: AIMessage[], externalSystem?: string): any[] {\n const out: any[] = []\n\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const combinedSystem = [externalSystem, systemFromMessages].filter(Boolean).join('\\n\\n')\n if (combinedSystem) {\n out.push({ role: 'system', content: combinedSystem })\n }\n\n for (const msg of messages) {\n if (msg.role === 'system') continue\n const sdkMsgs = toSDKMessages(msg)\n out.push(...sdkMsgs)\n }\n\n return out\n}\n\n/**\n * 한 AIMessage 가 다중 tool_result 를 가지면 OpenAI 측에서 여러 role:'tool' 메시지로\n * 분리해야 함 (OpenAI 는 메시지당 tool_call_id 하나만 매핑). 따라서 array 를 반환.\n */\nexport function toSDKMessages(msg: AIMessage): any[] {\n if (typeof msg.content === 'string') {\n return [{ role: msg.role, content: msg.content }]\n }\n\n const parts = msg.content\n const toolUseParts = parts.filter(p => p.type === 'tool_use') as any[]\n const toolResultParts = parts.filter(p => p.type === 'tool_result') as any[]\n const otherParts = parts.filter(p => p.type !== 'tool_use' && p.type !== 'tool_result')\n\n // tool_result 가 있으면 각각을 role='tool' 메시지로 분리 (OpenAI 요구사항).\n if (toolResultParts.length > 0) {\n const results = toolResultParts.map((r: any) => ({\n role: 'tool',\n tool_call_id: r.toolUseId,\n content: r.content\n }))\n /* A tool message holds text only - pictures a read tool returned follow as one user message (agentic-loop takeToolImages). */\n const images = otherParts.filter(p => p.type === 'image')\n return images.length ? [...results, { role: 'user', content: images.map(toSDKContentPart) }] : results\n }\n\n // assistant 측 tool_use 가 있으면 message.tool_calls 로\n const out: any = { role: msg.role }\n if (otherParts.length > 0) {\n out.content = otherParts.map(toSDKContentPart)\n } else {\n out.content = null\n }\n if (toolUseParts.length > 0) {\n out.tool_calls = toolUseParts.map(p => ({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: JSON.stringify(p.arguments) }\n }))\n }\n return [out]\n}\n\nfunction toSDKContentPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use' || part.type === 'tool_result') {\n // 처리는 toSDKMessage 가 함 — 도달하면 버그.\n return { type: 'text', text: '' }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image_url', image_url: { url: part.source.url } }\n }\n const b64 = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image_url',\n image_url: { url: `data:${part.source.mediaType};base64,${b64}` }\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 buildOpenAIToolChoice(options: AIToolChatOptions): any {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') return 'none'\n if (choice === 'required') {\n if (options.forceTool) {\n return { type: 'function', function: { name: options.forceTool } }\n }\n return 'required'\n }\n return 'auto'\n}\n\nfunction normalizeOpenAIStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_calls':\n return 'tool_use'\n case 'function_call':\n return 'tool_use'\n case 'stop':\n return 'end_turn'\n case 'length':\n return 'max_tokens'\n case 'content_filter':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('openai', config => new OpenAIAIClient(config))\n"]}
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../server/service/providers/openai.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI AI Client adapter.\n *\n * 기본 모델: gpt-4o (구성에서 override 가능).\n * SDK: openai\n *\n * organization 필드 지원.\n */\nimport OpenAI from 'openai'\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'\nimport { requestOptionsOf } from './request-options.js'\n\nconst DEFAULT_MODEL = 'gpt-4o'\nconst DEFAULT_MAX_TOKENS = 4096\n\nexport class OpenAIAIClient implements AIClient {\n readonly id: string\n private sdk: OpenAI\n private defaultModel: string\n\n constructor(config: AIClientConfig) {\n this.sdk = new OpenAI({\n apiKey: config.apiKey,\n ...(config.organization && { organization: config.organization }),\n ...(config.baseURL && { baseURL: config.baseURL })\n })\n this.defaultModel = config.model ?? DEFAULT_MODEL\n this.id = `openai:${this.defaultModel}`\n }\n\n async chat(messages: AIMessage[], options: AIChatOptions = {}): Promise<string> {\n const sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any\n }, requestOptionsOf(options))\n reportUsage(response, options, options.model ?? this.defaultModel)\n return response.choices[0]?.message?.content ?? ''\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 sdkMessages = buildSDKMessages(messages, enforcedSystem)\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n response_format: { type: 'json_object' },\n messages: sdkMessages as any\n }, requestOptionsOf(options))\n reportUsage(response, options, options.model ?? this.defaultModel)\n const text = response.choices[0]?.message?.content ?? '{}'\n return parseJSONResponse<T>(text, { cutShort: response.choices[0]?.finish_reason === 'length' })\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 sdkMessages = buildSDKMessages(messages, options.systemPrompt)\n\n const sdkTools = tools.map(t => ({\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description,\n parameters: t.parameters\n }\n }))\n\n const toolChoice = buildOpenAIToolChoice(options)\n\n const response = await this.sdk.chat.completions.create({\n model: options.model ?? this.defaultModel,\n /* `max_tokens` is refused by the gpt-5 and o-series models; every chat model takes this one. */\n max_completion_tokens: options.maxTokens ?? DEFAULT_MAX_TOKENS,\n ...(options.temperature !== undefined && { temperature: options.temperature }),\n ...(options.stopSequences && { stop: options.stopSequences }),\n messages: sdkMessages as any,\n tools: sdkTools,\n ...(toolChoice !== undefined && { tool_choice: toolChoice as any }),\n ...(options.allowParallelToolCalls === false && { parallel_tool_calls: false })\n }, requestOptionsOf(options))\n reportUsage(response, options, options.model ?? this.defaultModel)\n\n const choice = response.choices?.[0]\n const message = choice?.message\n const text = message?.content || undefined\n\n const toolCalls: AIToolCall[] = (message?.tool_calls || []).map((tc: any) => {\n const fn = tc.function\n let args: Record<string, any> = {}\n try {\n args = fn?.arguments ? JSON.parse(fn.arguments) : {}\n } catch {\n args = {}\n }\n return {\n id: typeof tc.id === 'string' && tc.id ? tc.id : `call_${Math.random().toString(36).slice(2, 12)}`,\n name: fn?.name ?? '',\n arguments: args\n }\n })\n\n return {\n text,\n toolCalls,\n stopReason: normalizeOpenAIStopReason(choice?.finish_reason)\n }\n }\n\n async listModels(): Promise<string[]> {\n const ids: string[] = []\n for await (const model of this.sdk.models.list()) ids.push(model.id)\n return ids.filter(isOpenAIChatModel).sort()\n }\n}\n\n/**\n * OpenAI's model list has no capability field, so the conversation models are told apart by name: the GPT and\n * o-series families, without their audio, realtime, speech, image, search and instruct variants. One rule, kept by a test.\n */\nexport function isOpenAIChatModel(id: string): boolean {\n if (!/^(gpt-|chatgpt-|o\\d)/.test(id)) return false\n return !/(audio|realtime|tts|transcribe|image|search|instruct|embedding|moderation)/.test(id)\n}\n\nfunction reportUsage(response: any, options: AIChatOptions, requested: string): void {\n options.onUsage?.({\n model: response?.model ?? requested,\n inputTokens: response?.usage?.prompt_tokens,\n outputTokens: response?.usage?.completion_tokens\n })\n}\n\n// ── 변환 유틸 ─────────────────────────────────────────────────────────\n\nfunction buildSDKMessages(messages: AIMessage[], externalSystem?: string): any[] {\n const out: any[] = []\n\n const systemFromMessages = messages\n .filter(m => m.role === 'system')\n .map(extractTextContent)\n .filter(Boolean)\n .join('\\n\\n')\n const combinedSystem = [externalSystem, systemFromMessages].filter(Boolean).join('\\n\\n')\n if (combinedSystem) {\n out.push({ role: 'system', content: combinedSystem })\n }\n\n for (const msg of messages) {\n if (msg.role === 'system') continue\n const sdkMsgs = toSDKMessages(msg)\n out.push(...sdkMsgs)\n }\n\n return out\n}\n\n/**\n * 한 AIMessage 가 다중 tool_result 를 가지면 OpenAI 측에서 여러 role:'tool' 메시지로\n * 분리해야 함 (OpenAI 는 메시지당 tool_call_id 하나만 매핑). 따라서 array 를 반환.\n */\nexport function toSDKMessages(msg: AIMessage): any[] {\n if (typeof msg.content === 'string') {\n return [{ role: msg.role, content: msg.content }]\n }\n\n const parts = msg.content\n const toolUseParts = parts.filter(p => p.type === 'tool_use') as any[]\n const toolResultParts = parts.filter(p => p.type === 'tool_result') as any[]\n const otherParts = parts.filter(p => p.type !== 'tool_use' && p.type !== 'tool_result')\n\n // tool_result 가 있으면 각각을 role='tool' 메시지로 분리 (OpenAI 요구사항).\n if (toolResultParts.length > 0) {\n const results = toolResultParts.map((r: any) => ({\n role: 'tool',\n tool_call_id: r.toolUseId,\n content: r.content\n }))\n /* A tool message holds text only - pictures a read tool returned follow as one user message (agentic-loop takeToolImages). */\n const images = otherParts.filter(p => p.type === 'image')\n return images.length ? [...results, { role: 'user', content: images.map(toSDKContentPart) }] : results\n }\n\n // assistant 측 tool_use 가 있으면 message.tool_calls 로\n const out: any = { role: msg.role }\n if (otherParts.length > 0) {\n out.content = otherParts.map(toSDKContentPart)\n } else {\n out.content = null\n }\n if (toolUseParts.length > 0) {\n out.tool_calls = toolUseParts.map(p => ({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: JSON.stringify(p.arguments) }\n }))\n }\n return [out]\n}\n\nfunction toSDKContentPart(part: AIContentPart): any {\n if (part.type === 'text') {\n return { type: 'text', text: part.text }\n }\n if (part.type === 'tool_use' || part.type === 'tool_result') {\n // 처리는 toSDKMessage 가 함 — 도달하면 버그.\n return { type: 'text', text: '' }\n }\n // image\n if (part.source.kind === 'url') {\n return { type: 'image_url', image_url: { url: part.source.url } }\n }\n const b64 = Buffer.isBuffer(part.source.data)\n ? part.source.data.toString('base64')\n : Buffer.from(part.source.data).toString('base64')\n return {\n type: 'image_url',\n image_url: { url: `data:${part.source.mediaType};base64,${b64}` }\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 buildOpenAIToolChoice(options: AIToolChatOptions): any {\n const choice = options.toolChoice ?? 'auto'\n if (choice === 'none') return 'none'\n if (choice === 'required') {\n if (options.forceTool) {\n return { type: 'function', function: { name: options.forceTool } }\n }\n return 'required'\n }\n return 'auto'\n}\n\nfunction normalizeOpenAIStopReason(raw: any): AIToolChatResult['stopReason'] {\n switch (raw) {\n case 'tool_calls':\n return 'tool_use'\n case 'function_call':\n return 'tool_use'\n case 'stop':\n return 'end_turn'\n case 'length':\n return 'max_tokens'\n case 'content_filter':\n return 'stop_sequence'\n default:\n return raw ?? 'end_turn'\n }\n}\n\n// Auto-register at module load\nregisterAIProvider('openai', config => new OpenAIAIClient(config))\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestOptionsOf = requestOptionsOf;
|
|
4
|
+
function requestOptionsOf(options) {
|
|
5
|
+
return options?.signal ? { signal: options.signal } : undefined;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=request-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-options.js","sourceRoot":"","sources":["../../../server/service/providers/request-options.ts"],"names":[],"mappings":";;AASA,4CAEC;AAFD,SAAgB,gBAAgB,CAAC,OAAkC;IACjE,OAAO,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;AACjE,CAAC","sourcesContent":["/*\n * What a provider SDK is handed per request besides the body - the caller's abort signal (types.ts `signal`).\n *\n * The three adapters each take it the same way, so it is said once. Aborting stops our wait and frees the loop; the\n * provider may still finish the request on its side and bill it, which is why a run's spend is capped by its token\n * limit and not by its clock.\n */\nimport type { AIChatOptions } from '../types.js'\n\nexport function requestOptionsOf(options: AIChatOptions | undefined): { signal: AbortSignal } | undefined {\n return options?.signal ? { signal: options.signal } : undefined\n}\n"]}
|