@waifucave/discord-waifus 1.5.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +230 -0
  3. package/THIRD_PARTY_NOTICES.md +26 -0
  4. package/assets/ocr/eng.traineddata +0 -0
  5. package/bin/waifus.mjs +36 -0
  6. package/dist/api/errors.d.ts +9 -0
  7. package/dist/api/errors.js +23 -0
  8. package/dist/api/errors.js.map +1 -0
  9. package/dist/api/server.d.ts +19 -0
  10. package/dist/api/server.js +1290 -0
  11. package/dist/api/server.js.map +1 -0
  12. package/dist/backend/logger.d.ts +20 -0
  13. package/dist/backend/logger.js +65 -0
  14. package/dist/backend/logger.js.map +1 -0
  15. package/dist/backend/migrations.d.ts +4 -0
  16. package/dist/backend/migrations.js +521 -0
  17. package/dist/backend/migrations.js.map +1 -0
  18. package/dist/backend/redaction.d.ts +1 -0
  19. package/dist/backend/redaction.js +22 -0
  20. package/dist/backend/redaction.js.map +1 -0
  21. package/dist/backend/runtime.d.ts +30 -0
  22. package/dist/backend/runtime.js +49 -0
  23. package/dist/backend/runtime.js.map +1 -0
  24. package/dist/backend/server.d.ts +18 -0
  25. package/dist/backend/server.js +581 -0
  26. package/dist/backend/server.js.map +1 -0
  27. package/dist/cli/commands.d.ts +35 -0
  28. package/dist/cli/commands.js +510 -0
  29. package/dist/cli/commands.js.map +1 -0
  30. package/dist/cli/main.d.ts +1 -0
  31. package/dist/cli/main.js +12 -0
  32. package/dist/cli/main.js.map +1 -0
  33. package/dist/cli/parser.d.ts +10 -0
  34. package/dist/cli/parser.js +64 -0
  35. package/dist/cli/parser.js.map +1 -0
  36. package/dist/config/appConfig.d.ts +3 -0
  37. package/dist/config/appConfig.js +27 -0
  38. package/dist/config/appConfig.js.map +1 -0
  39. package/dist/config/layout.d.ts +13 -0
  40. package/dist/config/layout.js +173 -0
  41. package/dist/config/layout.js.map +1 -0
  42. package/dist/config/paths.d.ts +5 -0
  43. package/dist/config/paths.js +20 -0
  44. package/dist/config/paths.js.map +1 -0
  45. package/dist/config/prebuiltWaifus.d.ts +3 -0
  46. package/dist/config/prebuiltWaifus.js +106 -0
  47. package/dist/config/prebuiltWaifus.js.map +1 -0
  48. package/dist/discord/client.d.ts +394 -0
  49. package/dist/discord/client.js +1196 -0
  50. package/dist/discord/client.js.map +1 -0
  51. package/dist/discord/contextBuilder.d.ts +15 -0
  52. package/dist/discord/contextBuilder.js +18 -0
  53. package/dist/discord/contextBuilder.js.map +1 -0
  54. package/dist/discord/intents.d.ts +5 -0
  55. package/dist/discord/intents.js +21 -0
  56. package/dist/discord/intents.js.map +1 -0
  57. package/dist/discord/memberCache.d.ts +12 -0
  58. package/dist/discord/memberCache.js +55 -0
  59. package/dist/discord/memberCache.js.map +1 -0
  60. package/dist/discord/normalization.d.ts +34 -0
  61. package/dist/discord/normalization.js +330 -0
  62. package/dist/discord/normalization.js.map +1 -0
  63. package/dist/discord/rateLimit.d.ts +9 -0
  64. package/dist/discord/rateLimit.js +11 -0
  65. package/dist/discord/rateLimit.js.map +1 -0
  66. package/dist/orchestration/context.d.ts +85 -0
  67. package/dist/orchestration/context.js +200 -0
  68. package/dist/orchestration/context.js.map +1 -0
  69. package/dist/orchestration/decisions.d.ts +52 -0
  70. package/dist/orchestration/decisions.js +61 -0
  71. package/dist/orchestration/decisions.js.map +1 -0
  72. package/dist/orchestration/messageSplit.d.ts +6 -0
  73. package/dist/orchestration/messageSplit.js +118 -0
  74. package/dist/orchestration/messageSplit.js.map +1 -0
  75. package/dist/orchestration/ocr.d.ts +43 -0
  76. package/dist/orchestration/ocr.js +504 -0
  77. package/dist/orchestration/ocr.js.map +1 -0
  78. package/dist/orchestration/ocrPackages.d.ts +24 -0
  79. package/dist/orchestration/ocrPackages.js +85 -0
  80. package/dist/orchestration/ocrPackages.js.map +1 -0
  81. package/dist/orchestration/promptBlocks.d.ts +28 -0
  82. package/dist/orchestration/promptBlocks.js +232 -0
  83. package/dist/orchestration/promptBlocks.js.map +1 -0
  84. package/dist/orchestration/replyQuote.d.ts +7 -0
  85. package/dist/orchestration/replyQuote.js +198 -0
  86. package/dist/orchestration/replyQuote.js.map +1 -0
  87. package/dist/orchestration/reviewer.d.ts +5 -0
  88. package/dist/orchestration/reviewer.js +5 -0
  89. package/dist/orchestration/reviewer.js.map +1 -0
  90. package/dist/orchestration/runtime.d.ts +152 -0
  91. package/dist/orchestration/runtime.js +3535 -0
  92. package/dist/orchestration/runtime.js.map +1 -0
  93. package/dist/orchestration/session.d.ts +30 -0
  94. package/dist/orchestration/session.js +41 -0
  95. package/dist/orchestration/session.js.map +1 -0
  96. package/dist/orchestration/stageManager.d.ts +42 -0
  97. package/dist/orchestration/stageManager.js +50 -0
  98. package/dist/orchestration/stageManager.js.map +1 -0
  99. package/dist/providers/catalog.d.ts +6 -0
  100. package/dist/providers/catalog.js +212 -0
  101. package/dist/providers/catalog.js.map +1 -0
  102. package/dist/providers/pipelines.d.ts +13 -0
  103. package/dist/providers/pipelines.js +2388 -0
  104. package/dist/providers/pipelines.js.map +1 -0
  105. package/dist/providers/types.d.ts +91 -0
  106. package/dist/providers/types.js +2 -0
  107. package/dist/providers/types.js.map +1 -0
  108. package/dist/shared/queryLog.d.ts +39 -0
  109. package/dist/shared/queryLog.js +80 -0
  110. package/dist/shared/queryLog.js.map +1 -0
  111. package/dist/shared/schemas/common.d.ts +21 -0
  112. package/dist/shared/schemas/common.js +27 -0
  113. package/dist/shared/schemas/common.js.map +1 -0
  114. package/dist/shared/schemas/config.d.ts +57 -0
  115. package/dist/shared/schemas/config.js +49 -0
  116. package/dist/shared/schemas/config.js.map +1 -0
  117. package/dist/shared/schemas/domain.d.ts +825 -0
  118. package/dist/shared/schemas/domain.js +444 -0
  119. package/dist/shared/schemas/domain.js.map +1 -0
  120. package/dist/storage/atomic.d.ts +6 -0
  121. package/dist/storage/atomic.js +60 -0
  122. package/dist/storage/atomic.js.map +1 -0
  123. package/dist/storage/errors.d.ts +12 -0
  124. package/dist/storage/errors.js +23 -0
  125. package/dist/storage/errors.js.map +1 -0
  126. package/dist/storage/locks.d.ts +9 -0
  127. package/dist/storage/locks.js +42 -0
  128. package/dist/storage/locks.js.map +1 -0
  129. package/dist/storage/storageService.d.ts +25 -0
  130. package/dist/storage/storageService.js +66 -0
  131. package/dist/storage/storageService.js.map +1 -0
  132. package/dist-frontend/assets/index-B2bB3_2r.js +39 -0
  133. package/dist-frontend/assets/index-B2bB3_2r.js.map +1 -0
  134. package/dist-frontend/assets/index-Dj1L8Px-.css +1 -0
  135. package/dist-frontend/index.html +14 -0
  136. package/docs/api.md +162 -0
  137. package/docs/old-orchestrator-prompt.md +123 -0
  138. package/package.json +81 -0
@@ -0,0 +1,2388 @@
1
+ import { z } from "zod";
2
+ import { formatOrchestratorMessageBlock, formatWaifuContextBlock } from "../orchestration/context.js";
3
+ import { OrchestratorActionSchema, OrchestratorDecisionSchema, MAX_WAIFU_DELAY_SECONDS, REPLY_STYLE_VALUES, ReplyStyleSchema, RETRIGGER_MAX_SECONDS, RETRIGGER_MIN_SECONDS } from "../orchestration/decisions.js";
4
+ import { ReviewerDecisionSchema } from "../orchestration/reviewer.js";
5
+ import { OBSERVATION_KINDS, StageManagerObservationSchema, StageManagerToolCallSchema } from "../orchestration/stageManager.js";
6
+ import { getModel, getProviderForModel } from "./catalog.js";
7
+ import { recordProviderQuery, recordProviderReply } from "../shared/queryLog.js";
8
+ export class ProviderPipelineError extends Error {
9
+ details;
10
+ constructor(message, details) {
11
+ super(message);
12
+ this.details = details;
13
+ this.name = "ProviderPipelineError";
14
+ }
15
+ }
16
+ export function createModelPipeline(modelId, credentials) {
17
+ const model = getModel(modelId);
18
+ const provider = getProviderForModel(modelId);
19
+ if (!model || !provider) {
20
+ throw new ProviderPipelineError(`Unknown model ${modelId}.`);
21
+ }
22
+ switch (model.client) {
23
+ case "openai-compatible-chat":
24
+ return new OpenAiCompatibleChatPipeline(provider, model, credentials.apiKey);
25
+ case "openai-responses":
26
+ return new OpenAiResponsesPipeline(provider, model, credentials.apiKey);
27
+ case "anthropic-messages":
28
+ return new AnthropicMessagesPipeline(provider, model, credentials.apiKey);
29
+ case "google-generative-language":
30
+ return new GoogleGenerativeLanguagePipeline(provider, model, credentials.apiKey);
31
+ }
32
+ }
33
+ class OpenAiCompatibleChatPipeline {
34
+ provider;
35
+ model;
36
+ apiKey;
37
+ constructor(provider, model, apiKey) {
38
+ this.provider = provider;
39
+ this.model = model;
40
+ this.apiKey = apiKey;
41
+ }
42
+ async generateWaifu(request) {
43
+ throwIfAborted(request.signal);
44
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
45
+ const reasoning = openAiChatReasoningForWaifu(this.model, request);
46
+ const result = await postJsonAndExtractText({
47
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
48
+ headers: bearerHeaders(this.apiKey),
49
+ body: {
50
+ model: request.modelId,
51
+ messages: openAiChatMessagesForModel(this.model, [
52
+ { role: "system", content: request.systemPrompt },
53
+ ...injectMemoriesIntoChatContext(contextToChatMessagesForWaifu(request.messages, this.model.supportsImageInput), request.midSystemBlock ? { role: "system", content: request.midSystemBlock } : undefined),
54
+ ...replyStyleMessagesForChat(request.replyStyle),
55
+ ...(request.trailingSystemBlock ? [{ role: "system", content: request.trailingSystemBlock }] : []),
56
+ ...(request.retryUserMessage ? [{ role: "user", content: request.retryUserMessage }] : [])
57
+ ]),
58
+ temperature: openAiChatTemperature(this.model, request.temperature ?? this.model.defaultTemperature),
59
+ top_p: openAiChatTopP(this.model, request.topP ?? this.model.defaultTopP),
60
+ max_tokens: request.maxOutputTokens,
61
+ ...openAiChatWaifuToolsPayload(this.model, request),
62
+ stop: openAiChatStopSequences(this.model, reasoning, request.stopSequences),
63
+ stream: false,
64
+ ...reasoningFieldsForOpenAiChat(this.model, reasoning),
65
+ ...openAiChatSamplingOverrides(this.model, reasoning)
66
+ },
67
+ signal: request.signal,
68
+ extract: (json) => extractOpenAiChatWaifuResult(json, request.availableWaifuIds),
69
+ queryRole: "waifu"
70
+ });
71
+ return result;
72
+ }
73
+ async decideOrchestrator(request) {
74
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
75
+ const reasoning = openAiChatReasoningForForcedTool(this.model, request.reasoning);
76
+ const text = await postJsonAndExtractText({
77
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
78
+ headers: bearerHeaders(this.apiKey),
79
+ body: {
80
+ model: request.modelId,
81
+ messages: openAiChatMessagesForModel(this.model, buildOpenAiChatOrchestratorMessages({
82
+ model: this.model,
83
+ systemPrompt: request.systemPrompt ?? "",
84
+ messages: request.messages,
85
+ decisions: request.pastDecisions ?? [],
86
+ trailingPrompt: request.trailingPrompt ?? ""
87
+ })),
88
+ temperature: openAiChatTemperature(this.model, request.temperature ?? 0.2),
89
+ top_p: openAiChatTopP(this.model, request.topP),
90
+ max_tokens: request.maxOutputTokens,
91
+ tools: [openAiChatOrchestratorTool(request.availableWaifuIds, request.replyRequired)],
92
+ tool_choice: openAiChatForcedToolChoice(this.model, ORCHESTRATOR_TOOL_NAME),
93
+ stream: false,
94
+ ...reasoningFieldsForOpenAiChat(this.model, reasoning),
95
+ ...openAiChatSamplingOverrides(this.model, reasoning)
96
+ },
97
+ signal: request.signal,
98
+ extract: (json) => extractOpenAiChatToolArguments(json, ORCHESTRATOR_TOOL_NAME),
99
+ queryRole: "orchestrator"
100
+ });
101
+ return parseDecision(text, new Map(), request.replyRequired);
102
+ }
103
+ async decideStageManagerObservations(request) {
104
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
105
+ const rendering = renderContext(request.messages);
106
+ const reasoning = openAiChatReasoningForForcedTool(this.model, request.reasoning);
107
+ const text = await postJsonAndExtractText({
108
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
109
+ headers: bearerHeaders(this.apiKey),
110
+ body: {
111
+ model: request.modelId,
112
+ messages: openAiChatMessagesForModel(this.model, [
113
+ { role: "system", content: observerSystemPrompt(request.systemPrompt, request.availableWaifuIds) },
114
+ contextToUserMessage(rendering)
115
+ ]),
116
+ temperature: openAiChatTemperature(this.model, request.temperature ?? 0.2),
117
+ top_p: openAiChatTopP(this.model, request.topP),
118
+ max_tokens: request.maxOutputTokens,
119
+ tools: [openAiChatObserverTool(request.availableWaifuIds)],
120
+ tool_choice: openAiChatForcedToolChoice(this.model, OBSERVER_TOOL_NAME),
121
+ stream: false,
122
+ ...reasoningFieldsForOpenAiChat(this.model, reasoning),
123
+ ...openAiChatSamplingOverrides(this.model, reasoning)
124
+ },
125
+ signal: request.signal,
126
+ extract: (json) => extractOpenAiChatToolArguments(json, OBSERVER_TOOL_NAME),
127
+ queryRole: "stage_manager_observer"
128
+ });
129
+ return parseStageManagerObservations(text);
130
+ }
131
+ async decideStageManager(request) {
132
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
133
+ const reasoning = openAiChatReasoningForForcedTool(this.model, request.reasoning);
134
+ const text = await postJsonAndExtractText({
135
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
136
+ headers: bearerHeaders(this.apiKey),
137
+ body: {
138
+ model: request.modelId,
139
+ messages: openAiChatMessagesForModel(this.model, [
140
+ { role: "system", content: librarianSystemPrompt(request.availableWaifuIds) },
141
+ { role: "user", content: `observations: ${JSON.stringify(request.observations ?? [])}` },
142
+ { role: "user", content: `memories: ${JSON.stringify(request.memories)}` }
143
+ ]),
144
+ temperature: openAiChatTemperature(this.model, request.temperature ?? 0.2),
145
+ top_p: openAiChatTopP(this.model, request.topP),
146
+ max_tokens: request.maxOutputTokens,
147
+ tools: [openAiChatStageManagerTool(request.availableWaifuIds)],
148
+ tool_choice: openAiChatForcedToolChoice(this.model, STAGE_MANAGER_TOOL_NAME),
149
+ stream: false,
150
+ ...reasoningFieldsForOpenAiChat(this.model, reasoning),
151
+ ...openAiChatSamplingOverrides(this.model, reasoning)
152
+ },
153
+ signal: request.signal,
154
+ extract: (json) => extractOpenAiChatToolArguments(json, STAGE_MANAGER_TOOL_NAME),
155
+ queryRole: "stage_manager_librarian"
156
+ });
157
+ return parseStageManagerCalls(text);
158
+ }
159
+ async decideReviewer(request) {
160
+ validateMaxOutputTokens(this.model, request.maxOutputTokens ?? 64);
161
+ const reasoning = openAiChatReasoningForForcedTool(this.model, request.reasoning);
162
+ const text = await postJsonAndExtractText({
163
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
164
+ headers: bearerHeaders(this.apiKey),
165
+ body: {
166
+ model: request.modelId,
167
+ messages: openAiChatMessagesForModel(this.model, [
168
+ { role: "system", content: reviewerSystemPrompt(request.systemPrompt) },
169
+ { role: "user", content: request.message }
170
+ ]),
171
+ temperature: openAiChatTemperature(this.model, request.temperature ?? 0),
172
+ top_p: openAiChatTopP(this.model, request.topP),
173
+ max_tokens: request.maxOutputTokens ?? 64,
174
+ tools: [openAiChatReviewerTool()],
175
+ tool_choice: openAiChatForcedToolChoice(this.model, REVIEWER_TOOL_NAME),
176
+ stream: false,
177
+ ...reasoningFieldsForOpenAiChat(this.model, reasoning),
178
+ ...openAiChatSamplingOverrides(this.model, reasoning)
179
+ },
180
+ signal: request.signal,
181
+ extract: (json) => extractOpenAiChatToolArguments(json, REVIEWER_TOOL_NAME),
182
+ queryRole: "reviewer"
183
+ });
184
+ return parseReviewerDecision(text);
185
+ }
186
+ }
187
+ class OpenAiResponsesPipeline {
188
+ provider;
189
+ model;
190
+ apiKey;
191
+ constructor(provider, model, apiKey) {
192
+ this.provider = provider;
193
+ this.model = model;
194
+ this.apiKey = apiKey;
195
+ }
196
+ async generateWaifu(request) {
197
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
198
+ const result = await postJsonAndExtractText({
199
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
200
+ headers: bearerHeaders(this.apiKey),
201
+ body: {
202
+ model: request.modelId,
203
+ instructions: request.systemPrompt,
204
+ input: [
205
+ ...injectMemoriesIntoChatContext(contextToResponsesInputForWaifu(request.messages, this.model.supportsImageInput), request.midSystemBlock ? { role: "system", content: request.midSystemBlock } : undefined),
206
+ ...replyStyleMessagesForChat(request.replyStyle),
207
+ ...(request.trailingSystemBlock ? [{ role: "system", content: request.trailingSystemBlock }] : []),
208
+ ...(request.retryUserMessage ? [{ role: "user", content: request.retryUserMessage }] : [])
209
+ ],
210
+ temperature: request.temperature ?? this.model.defaultTemperature,
211
+ top_p: request.topP ?? this.model.defaultTopP,
212
+ max_output_tokens: request.maxOutputTokens,
213
+ ...openAiResponsesWaifuToolsPayload(this.model, request),
214
+ ...reasoningFieldsForOpenAiResponses(this.model, request.reasoning),
215
+ ...openAiResponsesSamplingOverrides(this.model)
216
+ },
217
+ signal: request.signal,
218
+ extract: (json) => extractOpenAiResponsesWaifuResult(json, request.availableWaifuIds),
219
+ queryRole: "waifu"
220
+ });
221
+ return result;
222
+ }
223
+ async decideOrchestrator(request) {
224
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
225
+ const text = await postJsonAndExtractText({
226
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
227
+ headers: bearerHeaders(this.apiKey),
228
+ body: {
229
+ model: request.modelId,
230
+ instructions: request.systemPrompt,
231
+ input: buildOpenAiResponsesOrchestratorInput({
232
+ messages: request.messages,
233
+ decisions: request.pastDecisions ?? [],
234
+ trailingPrompt: request.trailingPrompt ?? ""
235
+ }),
236
+ temperature: request.temperature ?? 0.2,
237
+ top_p: request.topP,
238
+ max_output_tokens: request.maxOutputTokens,
239
+ tools: [openAiResponsesOrchestratorTool(request.availableWaifuIds, request.replyRequired)],
240
+ tool_choice: { type: "function", name: ORCHESTRATOR_TOOL_NAME },
241
+ ...reasoningFieldsForOpenAiResponses(this.model, request.reasoning),
242
+ ...openAiResponsesSamplingOverrides(this.model)
243
+ },
244
+ signal: request.signal,
245
+ extract: (json) => extractOpenAiResponsesToolArguments(json, ORCHESTRATOR_TOOL_NAME),
246
+ queryRole: "orchestrator"
247
+ });
248
+ return parseDecision(text, new Map(), request.replyRequired);
249
+ }
250
+ async decideStageManagerObservations(request) {
251
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
252
+ const rendering = renderContext(request.messages);
253
+ const text = await postJsonAndExtractText({
254
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
255
+ headers: bearerHeaders(this.apiKey),
256
+ body: {
257
+ model: request.modelId,
258
+ instructions: observerSystemPrompt(request.systemPrompt, request.availableWaifuIds),
259
+ input: [contextToUserMessage(rendering)],
260
+ temperature: request.temperature ?? 0.2,
261
+ top_p: request.topP,
262
+ max_output_tokens: request.maxOutputTokens,
263
+ tools: [openAiResponsesObserverTool(request.availableWaifuIds)],
264
+ tool_choice: { type: "function", name: OBSERVER_TOOL_NAME },
265
+ ...reasoningFieldsForOpenAiResponses(this.model, request.reasoning),
266
+ ...openAiResponsesSamplingOverrides(this.model)
267
+ },
268
+ signal: request.signal,
269
+ extract: (json) => extractOpenAiResponsesToolArguments(json, OBSERVER_TOOL_NAME),
270
+ queryRole: "stage_manager_observer"
271
+ });
272
+ return parseStageManagerObservations(text);
273
+ }
274
+ async decideStageManager(request) {
275
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
276
+ const text = await postJsonAndExtractText({
277
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
278
+ headers: bearerHeaders(this.apiKey),
279
+ body: {
280
+ model: request.modelId,
281
+ instructions: librarianSystemPrompt(request.availableWaifuIds),
282
+ input: [
283
+ { role: "user", content: `observations: ${JSON.stringify(request.observations ?? [])}` },
284
+ { role: "user", content: `memories: ${JSON.stringify(request.memories)}` }
285
+ ],
286
+ temperature: request.temperature ?? 0.2,
287
+ top_p: request.topP,
288
+ max_output_tokens: request.maxOutputTokens,
289
+ tools: [openAiResponsesStageManagerTool(request.availableWaifuIds)],
290
+ tool_choice: { type: "function", name: STAGE_MANAGER_TOOL_NAME },
291
+ ...reasoningFieldsForOpenAiResponses(this.model, request.reasoning),
292
+ ...openAiResponsesSamplingOverrides(this.model)
293
+ },
294
+ signal: request.signal,
295
+ extract: (json) => extractOpenAiResponsesToolArguments(json, STAGE_MANAGER_TOOL_NAME),
296
+ queryRole: "stage_manager_librarian"
297
+ });
298
+ return parseStageManagerCalls(text);
299
+ }
300
+ async decideReviewer(request) {
301
+ validateMaxOutputTokens(this.model, request.maxOutputTokens ?? 64);
302
+ const text = await postJsonAndExtractText({
303
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
304
+ headers: bearerHeaders(this.apiKey),
305
+ body: {
306
+ model: request.modelId,
307
+ instructions: reviewerSystemPrompt(request.systemPrompt),
308
+ input: [{ role: "user", content: request.message }],
309
+ temperature: request.temperature ?? 0,
310
+ top_p: request.topP,
311
+ max_output_tokens: request.maxOutputTokens ?? 64,
312
+ tools: [openAiResponsesReviewerTool()],
313
+ tool_choice: { type: "function", name: REVIEWER_TOOL_NAME },
314
+ ...reasoningFieldsForOpenAiResponses(this.model, request.reasoning),
315
+ ...openAiResponsesSamplingOverrides(this.model)
316
+ },
317
+ signal: request.signal,
318
+ extract: (json) => extractOpenAiResponsesToolArguments(json, REVIEWER_TOOL_NAME),
319
+ queryRole: "reviewer"
320
+ });
321
+ return parseReviewerDecision(text);
322
+ }
323
+ }
324
+ class AnthropicMessagesPipeline {
325
+ provider;
326
+ model;
327
+ apiKey;
328
+ constructor(provider, model, apiKey) {
329
+ this.provider = provider;
330
+ this.model = model;
331
+ this.apiKey = apiKey;
332
+ }
333
+ async generateWaifu(request) {
334
+ const maxTokens = request.maxOutputTokens ?? anthropicDefaultMaxTokens(this.model, request.reasoning);
335
+ validateMaxOutputTokens(this.model, maxTokens);
336
+ const thinking = anthropicThinkingPayload(this.model, request.reasoning, maxTokens);
337
+ const result = await postJsonAndExtractText({
338
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
339
+ headers: anthropicHeaders(this.apiKey),
340
+ body: {
341
+ model: request.modelId,
342
+ system: request.systemPrompt,
343
+ messages: [
344
+ ...injectMemoriesIntoChatContext(contextToAnthropicMessagesForWaifu(request.messages, this.model.supportsImageInput), request.midSystemBlock ? { role: "user", content: request.midSystemBlock } : undefined),
345
+ ...replyStyleMessagesForAnthropic(request.replyStyle),
346
+ ...(request.trailingSystemBlock ? [{ role: "user", content: request.trailingSystemBlock }] : []),
347
+ ...(request.retryUserMessage ? [{ role: "user", content: request.retryUserMessage }] : [])
348
+ ],
349
+ ...anthropicSamplingPayload(this.model, request.temperature ?? this.model.defaultTemperature, request.topP ?? this.model.defaultTopP, thinking),
350
+ max_tokens: maxTokens,
351
+ ...anthropicWaifuToolsPayload(this.model, request),
352
+ stop_sequences: request.stopSequences?.length ? request.stopSequences : undefined,
353
+ ...anthropicOutputConfig(this.model, request.reasoning, thinking),
354
+ ...(thinking ? { thinking } : {})
355
+ },
356
+ signal: request.signal,
357
+ extract: (json) => extractAnthropicWaifuResult(json, request.availableWaifuIds),
358
+ queryRole: "waifu"
359
+ });
360
+ return result;
361
+ }
362
+ async decideOrchestrator(request) {
363
+ const maxTokens = request.maxOutputTokens ?? 1024;
364
+ validateMaxOutputTokens(this.model, maxTokens);
365
+ const text = await postJsonAndExtractText({
366
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
367
+ headers: anthropicHeaders(this.apiKey),
368
+ body: {
369
+ model: request.modelId,
370
+ system: request.systemPrompt,
371
+ messages: buildAnthropicOrchestratorMessages({
372
+ messages: request.messages,
373
+ decisions: request.pastDecisions ?? [],
374
+ trailingPrompt: request.trailingPrompt ?? ""
375
+ }),
376
+ ...anthropicSamplingPayload(this.model, request.temperature ?? 0.2, request.topP, undefined),
377
+ max_tokens: maxTokens,
378
+ tools: [anthropicOrchestratorTool(request.availableWaifuIds, request.replyRequired)],
379
+ tool_choice: { type: "tool", name: ORCHESTRATOR_TOOL_NAME }
380
+ },
381
+ signal: request.signal,
382
+ extract: (json) => extractAnthropicToolArguments(json, ORCHESTRATOR_TOOL_NAME),
383
+ queryRole: "orchestrator"
384
+ });
385
+ return parseDecision(text, new Map(), request.replyRequired);
386
+ }
387
+ async decideStageManagerObservations(request) {
388
+ const rendering = renderContext(request.messages);
389
+ const maxTokens = request.maxOutputTokens ?? 1024;
390
+ validateMaxOutputTokens(this.model, maxTokens);
391
+ const text = await postJsonAndExtractText({
392
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
393
+ headers: anthropicHeaders(this.apiKey),
394
+ body: {
395
+ model: request.modelId,
396
+ system: observerSystemPrompt(request.systemPrompt, request.availableWaifuIds),
397
+ messages: [contextToUserMessage(rendering)],
398
+ ...anthropicSamplingPayload(this.model, request.temperature ?? 0.2, request.topP, undefined),
399
+ max_tokens: maxTokens,
400
+ tools: [anthropicObserverTool(request.availableWaifuIds)],
401
+ tool_choice: { type: "tool", name: OBSERVER_TOOL_NAME }
402
+ },
403
+ signal: request.signal,
404
+ extract: (json) => extractAnthropicToolArguments(json, OBSERVER_TOOL_NAME),
405
+ queryRole: "stage_manager_observer"
406
+ });
407
+ return parseStageManagerObservations(text);
408
+ }
409
+ async decideStageManager(request) {
410
+ const maxTokens = request.maxOutputTokens ?? 1024;
411
+ validateMaxOutputTokens(this.model, maxTokens);
412
+ const text = await postJsonAndExtractText({
413
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
414
+ headers: anthropicHeaders(this.apiKey),
415
+ body: {
416
+ model: request.modelId,
417
+ system: librarianSystemPrompt(request.availableWaifuIds),
418
+ messages: [
419
+ { role: "user", content: `observations: ${JSON.stringify(request.observations ?? [])}` },
420
+ { role: "user", content: `memories: ${JSON.stringify(request.memories)}` }
421
+ ],
422
+ ...anthropicSamplingPayload(this.model, request.temperature ?? 0.2, request.topP, undefined),
423
+ max_tokens: maxTokens,
424
+ tools: [anthropicStageManagerTool(request.availableWaifuIds)],
425
+ tool_choice: { type: "tool", name: STAGE_MANAGER_TOOL_NAME }
426
+ },
427
+ signal: request.signal,
428
+ extract: (json) => extractAnthropicToolArguments(json, STAGE_MANAGER_TOOL_NAME),
429
+ queryRole: "stage_manager_librarian"
430
+ });
431
+ return parseStageManagerCalls(text);
432
+ }
433
+ async decideReviewer(request) {
434
+ const maxTokens = request.maxOutputTokens ?? 256;
435
+ validateMaxOutputTokens(this.model, maxTokens);
436
+ const text = await postJsonAndExtractText({
437
+ url: `${this.provider.baseUrl}${this.model.endpoint}`,
438
+ headers: anthropicHeaders(this.apiKey),
439
+ body: {
440
+ model: request.modelId,
441
+ system: reviewerSystemPrompt(request.systemPrompt),
442
+ messages: [{ role: "user", content: request.message }],
443
+ ...anthropicSamplingPayload(this.model, request.temperature ?? 0, request.topP, undefined),
444
+ max_tokens: maxTokens,
445
+ tools: [anthropicReviewerTool()],
446
+ tool_choice: { type: "tool", name: REVIEWER_TOOL_NAME }
447
+ },
448
+ signal: request.signal,
449
+ extract: (json) => extractAnthropicToolArguments(json, REVIEWER_TOOL_NAME),
450
+ queryRole: "reviewer"
451
+ });
452
+ return parseReviewerDecision(text);
453
+ }
454
+ }
455
+ class GoogleGenerativeLanguagePipeline {
456
+ provider;
457
+ model;
458
+ apiKey;
459
+ constructor(provider, model, apiKey) {
460
+ this.provider = provider;
461
+ this.model = model;
462
+ this.apiKey = apiKey;
463
+ }
464
+ async generateWaifu(request) {
465
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
466
+ const contextContents = await contextToGoogleMessagesForWaifu(request.messages, this.model.supportsImageInput);
467
+ const replyHint = replyStyleHint(request.replyStyle);
468
+ const contents = [
469
+ ...injectMemoriesIntoChatContext(contextContents, request.midSystemBlock ? googleUserTurn(request.midSystemBlock) : undefined),
470
+ ...(replyHint ? [googleUserTurn(replyHint)] : []),
471
+ ...(request.trailingSystemBlock ? [googleUserTurn(request.trailingSystemBlock)] : []),
472
+ ...(request.retryUserMessage ? [googleUserTurn(request.retryUserMessage)] : [])
473
+ ];
474
+ const result = await postJsonAndExtractText({
475
+ url: googleAiStudioUrl(this.provider, this.model),
476
+ headers: googleAiStudioHeaders(this.apiKey),
477
+ body: stripUndefined({
478
+ systemInstruction: request.systemPrompt ? { parts: [{ text: request.systemPrompt }] } : undefined,
479
+ contents,
480
+ generationConfig: googleGenerationConfig(this.model, {
481
+ temperature: request.temperature ?? this.model.defaultTemperature,
482
+ topP: request.topP ?? this.model.defaultTopP,
483
+ maxOutputTokens: request.maxOutputTokens,
484
+ stopSequences: request.stopSequences,
485
+ reasoning: request.reasoning
486
+ }),
487
+ safetySettings: googleSafetySettings,
488
+ ...googleAiStudioWaifuToolsPayload(this.model, request)
489
+ }),
490
+ signal: request.signal,
491
+ extract: (json) => extractGoogleWaifuResult(json, request.availableWaifuIds),
492
+ queryRole: "waifu"
493
+ });
494
+ return result;
495
+ }
496
+ async decideOrchestrator(request) {
497
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
498
+ const text = await postJsonAndExtractText({
499
+ url: googleAiStudioUrl(this.provider, this.model),
500
+ headers: googleAiStudioHeaders(this.apiKey),
501
+ body: stripUndefined({
502
+ systemInstruction: request.systemPrompt ? { parts: [{ text: request.systemPrompt }] } : undefined,
503
+ contents: buildGoogleOrchestratorContents({
504
+ messages: request.messages,
505
+ decisions: request.pastDecisions ?? [],
506
+ trailingPrompt: request.trailingPrompt ?? ""
507
+ }),
508
+ generationConfig: googleGenerationConfig(this.model, {
509
+ temperature: request.temperature ?? 0.2,
510
+ topP: request.topP,
511
+ maxOutputTokens: request.maxOutputTokens,
512
+ reasoning: request.reasoning
513
+ }),
514
+ safetySettings: googleSafetySettings,
515
+ tools: [googleAiStudioOrchestratorTool(request.availableWaifuIds, request.replyRequired)],
516
+ toolConfig: googleForceToolConfig(ORCHESTRATOR_TOOL_NAME)
517
+ }),
518
+ signal: request.signal,
519
+ extract: (json) => extractGoogleToolArguments(json, ORCHESTRATOR_TOOL_NAME),
520
+ queryRole: "orchestrator"
521
+ });
522
+ return parseDecision(text, new Map(), request.replyRequired);
523
+ }
524
+ async decideStageManagerObservations(request) {
525
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
526
+ const rendering = renderContext(request.messages);
527
+ const text = await postJsonAndExtractText({
528
+ url: googleAiStudioUrl(this.provider, this.model),
529
+ headers: googleAiStudioHeaders(this.apiKey),
530
+ body: stripUndefined({
531
+ systemInstruction: { parts: [{ text: observerSystemPrompt(request.systemPrompt, request.availableWaifuIds) }] },
532
+ contents: [googleUserTurn(rendering.block)],
533
+ generationConfig: googleGenerationConfig(this.model, {
534
+ temperature: request.temperature ?? 0.2,
535
+ topP: request.topP,
536
+ maxOutputTokens: request.maxOutputTokens,
537
+ reasoning: request.reasoning
538
+ }),
539
+ safetySettings: googleSafetySettings,
540
+ tools: [googleAiStudioObserverTool(request.availableWaifuIds)],
541
+ toolConfig: googleForceToolConfig(OBSERVER_TOOL_NAME)
542
+ }),
543
+ signal: request.signal,
544
+ extract: (json) => extractGoogleToolArguments(json, OBSERVER_TOOL_NAME),
545
+ queryRole: "stage_manager_observer"
546
+ });
547
+ return parseStageManagerObservations(text);
548
+ }
549
+ async decideStageManager(request) {
550
+ validateMaxOutputTokens(this.model, request.maxOutputTokens);
551
+ const text = await postJsonAndExtractText({
552
+ url: googleAiStudioUrl(this.provider, this.model),
553
+ headers: googleAiStudioHeaders(this.apiKey),
554
+ body: stripUndefined({
555
+ systemInstruction: { parts: [{ text: librarianSystemPrompt(request.availableWaifuIds) }] },
556
+ contents: [
557
+ googleUserTurn(`observations: ${JSON.stringify(request.observations ?? [])}`),
558
+ googleUserTurn(`memories: ${JSON.stringify(request.memories)}`)
559
+ ],
560
+ generationConfig: googleGenerationConfig(this.model, {
561
+ temperature: request.temperature ?? 0.2,
562
+ topP: request.topP,
563
+ maxOutputTokens: request.maxOutputTokens,
564
+ reasoning: request.reasoning
565
+ }),
566
+ safetySettings: googleSafetySettings,
567
+ tools: [googleAiStudioStageManagerTool(request.availableWaifuIds)],
568
+ toolConfig: googleForceToolConfig(STAGE_MANAGER_TOOL_NAME)
569
+ }),
570
+ signal: request.signal,
571
+ extract: (json) => extractGoogleToolArguments(json, STAGE_MANAGER_TOOL_NAME),
572
+ queryRole: "stage_manager_librarian"
573
+ });
574
+ return parseStageManagerCalls(text);
575
+ }
576
+ async decideReviewer(request) {
577
+ validateMaxOutputTokens(this.model, request.maxOutputTokens ?? 64);
578
+ const text = await postJsonAndExtractText({
579
+ url: googleAiStudioUrl(this.provider, this.model),
580
+ headers: googleAiStudioHeaders(this.apiKey),
581
+ body: stripUndefined({
582
+ systemInstruction: { parts: [{ text: reviewerSystemPrompt(request.systemPrompt) }] },
583
+ contents: [googleUserTurn(request.message)],
584
+ generationConfig: googleGenerationConfig(this.model, {
585
+ temperature: request.temperature ?? 0,
586
+ topP: request.topP,
587
+ maxOutputTokens: request.maxOutputTokens ?? 64,
588
+ reasoning: request.reasoning
589
+ }),
590
+ safetySettings: googleSafetySettings,
591
+ tools: [googleAiStudioReviewerTool()],
592
+ toolConfig: googleForceToolConfig(REVIEWER_TOOL_NAME)
593
+ }),
594
+ signal: request.signal,
595
+ extract: (json) => extractGoogleToolArguments(json, REVIEWER_TOOL_NAME),
596
+ queryRole: "reviewer"
597
+ });
598
+ return parseReviewerDecision(text);
599
+ }
600
+ }
601
+ async function postJsonAndExtractText(options) {
602
+ throwIfAborted(options.signal);
603
+ const body = stripUndefined(options.body);
604
+ const query = recordProviderQuery(options.queryRole, body);
605
+ const requestSignal = providerRequestSignal(options.signal);
606
+ try {
607
+ const response = await fetch(options.url, {
608
+ method: "POST",
609
+ headers: {
610
+ "content-type": "application/json",
611
+ ...options.headers
612
+ },
613
+ body: JSON.stringify(body),
614
+ signal: requestSignal.signal
615
+ });
616
+ const text = await response.text();
617
+ let json;
618
+ let replyPayload;
619
+ try {
620
+ json = text ? JSON.parse(text) : undefined;
621
+ replyPayload = json ?? null;
622
+ }
623
+ catch {
624
+ json = { raw: text.slice(0, 1000) };
625
+ replyPayload = { raw: text };
626
+ }
627
+ recordProviderReply(options.queryRole, query.id, response.status, response.ok, replyPayload);
628
+ if (!response.ok) {
629
+ throw new ProviderPipelineError(`Provider request failed with HTTP ${response.status}.`, json);
630
+ }
631
+ const extracted = options.extract(json);
632
+ if (typeof extracted === "string") {
633
+ const content = extracted.trim();
634
+ if (!content) {
635
+ throw new ProviderPipelineError("Provider returned an empty response.", json);
636
+ }
637
+ return content;
638
+ }
639
+ if (isRecord(extracted) && typeof extracted.content === "string") {
640
+ const content = extracted.content.trim();
641
+ return { ...extracted, content };
642
+ }
643
+ if (extracted === undefined || extracted === null) {
644
+ throw new ProviderPipelineError("Provider returned an empty response.", json);
645
+ }
646
+ return extracted;
647
+ }
648
+ finally {
649
+ requestSignal.cleanup();
650
+ }
651
+ }
652
+ function isRecord(value) {
653
+ return typeof value === "object" && value !== null && !Array.isArray(value);
654
+ }
655
+ function providerRequestSignal(parent, timeoutMs = 180_000) {
656
+ const controller = new AbortController();
657
+ const timeout = setTimeout(() => {
658
+ controller.abort(new ProviderPipelineError(`Provider request timed out after ${timeoutMs / 1000}s.`));
659
+ }, timeoutMs);
660
+ const abortFromParent = () => {
661
+ controller.abort(parent?.reason instanceof Error ? parent.reason : new Error("Provider request aborted."));
662
+ };
663
+ if (parent) {
664
+ if (parent.aborted) {
665
+ abortFromParent();
666
+ }
667
+ else {
668
+ parent.addEventListener("abort", abortFromParent, { once: true });
669
+ }
670
+ }
671
+ return {
672
+ signal: controller.signal,
673
+ cleanup: () => {
674
+ clearTimeout(timeout);
675
+ parent?.removeEventListener("abort", abortFromParent);
676
+ }
677
+ };
678
+ }
679
+ function renderContext(messages, markers = []) {
680
+ const idToIndex = new Map();
681
+ const indexToId = new Map();
682
+ messages.forEach((message, i) => {
683
+ const index = i + 1;
684
+ idToIndex.set(message.id, index);
685
+ indexToId.set(index, message.id);
686
+ });
687
+ const items = [
688
+ ...messages.map((message, i) => ({ kind: "message", message, index: i + 1 })),
689
+ ...markers.map((marker) => ({ kind: "marker", marker }))
690
+ ];
691
+ items.sort((a, b) => {
692
+ const ta = a.kind === "message" ? a.message.timestamp : a.marker.timestamp;
693
+ const tb = b.kind === "message" ? b.message.timestamp : b.marker.timestamp;
694
+ if (ta === tb) {
695
+ if (a.kind === b.kind)
696
+ return 0;
697
+ return a.kind === "message" ? -1 : 1;
698
+ }
699
+ return ta < tb ? -1 : 1;
700
+ });
701
+ const lines = items.map((item) => item.kind === "message"
702
+ ? formatContextMessage(item.message, item.index, idToIndex)
703
+ : formatNoReplyMarker(item.marker));
704
+ return { block: lines.join("\n"), idToIndex, indexToId };
705
+ }
706
+ function formatNoReplyMarker(marker) {
707
+ const reason = marker.reasoning.replace(/\s+/g, " ").trim();
708
+ return `[no_reply] [timestamp: ${marker.timestamp}] [reason: ${reason}] [retrigger: ${marker.retriggerAfterSeconds}s]`;
709
+ }
710
+ function currentTimeBlock() {
711
+ return `<current_time>\n${formatPromptCurrentHour(new Date())}\n</current_time>`;
712
+ }
713
+ function formatPromptCurrentHour(date) {
714
+ return [
715
+ String(date.getFullYear()).padStart(4, "0"),
716
+ String(date.getMonth() + 1).padStart(2, "0"),
717
+ String(date.getDate()).padStart(2, "0")
718
+ ].join("-") + `T${String(date.getHours()).padStart(2, "0")}`;
719
+ }
720
+ function contextToUserMessage(rendering) {
721
+ return {
722
+ role: "user",
723
+ content: rendering.block
724
+ };
725
+ }
726
+ const ORCHESTRATOR_TOOL_RESULT_PLACEHOLDER = "ok";
727
+ function buildOrchestratorTimeline(messages, decisions) {
728
+ const oldestMessageTimestamp = messages.length ? messages[0].timestamp : undefined;
729
+ const items = [
730
+ ...messages.map((message) => ({
731
+ kind: "message",
732
+ message,
733
+ timestamp: message.timestamp
734
+ })),
735
+ ...decisions
736
+ .filter((decision) => oldestMessageTimestamp === undefined ? false : decision.createdAt >= oldestMessageTimestamp)
737
+ .map((decision) => ({
738
+ kind: "decision",
739
+ decision,
740
+ timestamp: decision.createdAt
741
+ }))
742
+ ];
743
+ items.sort((a, b) => {
744
+ if (a.timestamp === b.timestamp) {
745
+ if (a.kind === b.kind)
746
+ return 0;
747
+ return a.kind === "message" ? -1 : 1;
748
+ }
749
+ return a.timestamp < b.timestamp ? -1 : 1;
750
+ });
751
+ return items;
752
+ }
753
+ function serializeOrchestratorDecisionArguments(decision) {
754
+ return {
755
+ action: decision.action,
756
+ respondingWaifus: decision.respondingWaifus.map((responder) => ({
757
+ waifuId: responder.waifuId,
758
+ delaySeconds: responder.delaySeconds,
759
+ replyStyle: responder.replyStyle,
760
+ repleyToMessageIndex: null,
761
+ sceneDirection: responder.sceneDirection ?? null
762
+ })),
763
+ retriggerAfterSeconds: decision.action === "no_reply" ? decision.retriggerAfterSeconds ?? null : null,
764
+ reasoning: decision.reasoning
765
+ };
766
+ }
767
+ function buildOpenAiChatOrchestratorMessages(input) {
768
+ const messages = [{ role: "system", content: input.systemPrompt }];
769
+ for (const item of buildOrchestratorTimeline(input.messages, input.decisions)) {
770
+ if (item.kind === "message") {
771
+ messages.push({ role: "user", content: formatOrchestratorMessageBlock(item.message) });
772
+ }
773
+ else {
774
+ const args = serializeOrchestratorDecisionArguments(item.decision);
775
+ messages.push(stripUndefined({
776
+ role: "assistant",
777
+ content: input.model.providerId === "zai" ? undefined : null,
778
+ tool_calls: [
779
+ {
780
+ id: item.decision.id,
781
+ type: "function",
782
+ function: {
783
+ name: ORCHESTRATOR_TOOL_NAME,
784
+ arguments: JSON.stringify(args)
785
+ }
786
+ }
787
+ ]
788
+ }));
789
+ messages.push({
790
+ role: "tool",
791
+ tool_call_id: item.decision.id,
792
+ content: ORCHESTRATOR_TOOL_RESULT_PLACEHOLDER
793
+ });
794
+ }
795
+ }
796
+ messages.push({ role: "user", content: input.trailingPrompt });
797
+ return messages;
798
+ }
799
+ function buildOpenAiResponsesOrchestratorInput(input) {
800
+ const items = [];
801
+ for (const item of buildOrchestratorTimeline(input.messages, input.decisions)) {
802
+ if (item.kind === "message") {
803
+ items.push({ role: "user", content: formatOrchestratorMessageBlock(item.message) });
804
+ }
805
+ else {
806
+ const args = serializeOrchestratorDecisionArguments(item.decision);
807
+ items.push({
808
+ type: "function_call",
809
+ call_id: item.decision.id,
810
+ name: ORCHESTRATOR_TOOL_NAME,
811
+ arguments: JSON.stringify(args)
812
+ });
813
+ items.push({
814
+ type: "function_call_output",
815
+ call_id: item.decision.id,
816
+ output: ORCHESTRATOR_TOOL_RESULT_PLACEHOLDER
817
+ });
818
+ }
819
+ }
820
+ items.push({
821
+ role: "user",
822
+ content: [{ type: "input_text", text: input.trailingPrompt }]
823
+ });
824
+ return items;
825
+ }
826
+ function buildAnthropicOrchestratorMessages(input) {
827
+ const result = [];
828
+ let userBlocks = [];
829
+ const flushUser = () => {
830
+ if (userBlocks.length > 0) {
831
+ result.push({ role: "user", content: userBlocks });
832
+ userBlocks = [];
833
+ }
834
+ };
835
+ for (const item of buildOrchestratorTimeline(input.messages, input.decisions)) {
836
+ if (item.kind === "message") {
837
+ userBlocks.push({ type: "text", text: formatOrchestratorMessageBlock(item.message) });
838
+ }
839
+ else {
840
+ flushUser();
841
+ const args = serializeOrchestratorDecisionArguments(item.decision);
842
+ result.push({
843
+ role: "assistant",
844
+ content: [
845
+ {
846
+ type: "tool_use",
847
+ id: item.decision.id,
848
+ name: ORCHESTRATOR_TOOL_NAME,
849
+ input: args
850
+ }
851
+ ]
852
+ });
853
+ userBlocks.push({
854
+ type: "tool_result",
855
+ tool_use_id: item.decision.id,
856
+ content: ORCHESTRATOR_TOOL_RESULT_PLACEHOLDER
857
+ });
858
+ }
859
+ }
860
+ userBlocks.push({ type: "text", text: input.trailingPrompt });
861
+ flushUser();
862
+ return result;
863
+ }
864
+ function buildGoogleOrchestratorContents(input) {
865
+ const contents = [];
866
+ for (const item of buildOrchestratorTimeline(input.messages, input.decisions)) {
867
+ if (item.kind === "message") {
868
+ contents.push(googleUserTurn(formatOrchestratorMessageBlock(item.message)));
869
+ }
870
+ else {
871
+ const args = serializeOrchestratorDecisionArguments(item.decision);
872
+ contents.push({
873
+ role: "model",
874
+ parts: [{ functionCall: { name: ORCHESTRATOR_TOOL_NAME, args } }]
875
+ });
876
+ contents.push({
877
+ role: "user",
878
+ parts: [
879
+ {
880
+ functionResponse: {
881
+ name: ORCHESTRATOR_TOOL_NAME,
882
+ response: { output: ORCHESTRATOR_TOOL_RESULT_PLACEHOLDER }
883
+ }
884
+ }
885
+ ]
886
+ });
887
+ }
888
+ }
889
+ contents.push(googleUserTurn(input.trailingPrompt));
890
+ return contents;
891
+ }
892
+ function replyStyleHint(replyStyle) {
893
+ if (!replyStyle || replyStyle === "normal")
894
+ return undefined;
895
+ return `<reply_style>${replyStyle}</reply_style>`;
896
+ }
897
+ function replyStyleMessagesForChat(replyStyle) {
898
+ const hint = replyStyleHint(replyStyle);
899
+ return hint ? [{ role: "system", content: hint }] : [];
900
+ }
901
+ function replyStyleMessagesForAnthropic(replyStyle) {
902
+ const hint = replyStyleHint(replyStyle);
903
+ return hint ? [{ role: "user", content: hint }] : [];
904
+ }
905
+ function contextToChatMessagesForWaifu(messages, includeImages) {
906
+ return messages.map((message) => {
907
+ const role = roleForWaifuContext(message);
908
+ const text = formatWaifuContextBlock(message);
909
+ const imageBlocks = includeImages && role === "user" ? chatImageBlocks(message) : [];
910
+ return {
911
+ role,
912
+ content: imageBlocks.length ? [{ type: "text", text }, ...imageBlocks] : text
913
+ };
914
+ });
915
+ }
916
+ function injectMemoriesIntoChatContext(contextMessages, memoriesMessage) {
917
+ if (!memoriesMessage)
918
+ return contextMessages;
919
+ const insertAt = Math.max(0, contextMessages.length - 2);
920
+ return [...contextMessages.slice(0, insertAt), memoriesMessage, ...contextMessages.slice(insertAt)];
921
+ }
922
+ function chatImageBlocks(message) {
923
+ return (message.images ?? []).map((image) => ({
924
+ type: "image_url",
925
+ image_url: { url: image.url }
926
+ }));
927
+ }
928
+ function contextToResponsesInputForWaifu(messages, includeImages) {
929
+ return messages.map((message) => {
930
+ const role = roleForWaifuContext(message);
931
+ const text = formatWaifuContextBlock(message);
932
+ const imageBlocks = includeImages && role === "user" ? responsesImageBlocks(message) : [];
933
+ return {
934
+ role,
935
+ content: imageBlocks.length ? [{ type: "input_text", text }, ...imageBlocks] : text
936
+ };
937
+ });
938
+ }
939
+ function responsesImageBlocks(message) {
940
+ return (message.images ?? []).map((image) => ({
941
+ type: "input_image",
942
+ image_url: image.url
943
+ }));
944
+ }
945
+ function contextToAnthropicMessagesForWaifu(messages, includeImages) {
946
+ return messages.map((message) => {
947
+ const role = roleForWaifuContext(message);
948
+ const text = formatWaifuContextBlock(message);
949
+ const imageBlocks = includeImages && role === "user" ? anthropicImageBlocks(message) : [];
950
+ return {
951
+ role,
952
+ content: imageBlocks.length ? [{ type: "text", text }, ...imageBlocks] : text
953
+ };
954
+ });
955
+ }
956
+ function anthropicImageBlocks(message) {
957
+ return (message.images ?? []).map((image) => ({
958
+ type: "image",
959
+ source: { type: "url", url: image.url }
960
+ }));
961
+ }
962
+ function roleForWaifuContext(message) {
963
+ return message.authorKind === "waifu" ? "assistant" : "user";
964
+ }
965
+ function formatContextMessage(message, index, idToIndex) {
966
+ const prefix = `[index: #${index}] [timestamp: ${message.timestamp}] ${message.displayName}:`;
967
+ const suffix = buildSuffix(message, idToIndex);
968
+ const body = message.content.length > 0 ? ` ${message.content}` : "";
969
+ return `${prefix}${body}${suffix}`;
970
+ }
971
+ function buildSuffix(message, idToIndex) {
972
+ const parts = [];
973
+ if (message.images?.length) {
974
+ parts.push(`[images: ${message.images.length}]`);
975
+ message.images.forEach((image, index) => {
976
+ const text = formatOcrText(image.ocrText);
977
+ if (text) {
978
+ parts.push(`[image_text #${index + 1}: ${text}]`);
979
+ }
980
+ });
981
+ }
982
+ if (message.reactions.length) {
983
+ parts.push(`[reactions: ${message.reactions.map((reaction) => `${reaction.emoji} x${reaction.count}`).join(", ")}]`);
984
+ }
985
+ if (message.replyTo) {
986
+ const referencedIndex = idToIndex?.get(message.replyTo.messageId);
987
+ if (referencedIndex !== undefined) {
988
+ parts.push(`[replying to: #${referencedIndex}]`);
989
+ }
990
+ else {
991
+ const author = message.replyTo.authorName ?? "unknown";
992
+ const preview = message.replyTo.contentPreview ?? "";
993
+ parts.push(`[replying to: ${author}: ${preview}]`.replace(/\s+\]$/, "]"));
994
+ }
995
+ }
996
+ return parts.length ? ` ${parts.join(" ")}` : "";
997
+ }
998
+ function formatOcrText(text) {
999
+ const normalized = text
1000
+ ?.replace(/\s+/g, " ")
1001
+ .replace(/[\u0000-\u001F\u007F]/g, "")
1002
+ .trim();
1003
+ return normalized || undefined;
1004
+ }
1005
+ function observerSystemPrompt(customPrompt, availableWaifuIds) {
1006
+ return [customPrompt?.trim(), observerInstruction(availableWaifuIds)].filter(Boolean).join("\n\n");
1007
+ }
1008
+ function observerInstruction(availableWaifuIds) {
1009
+ const waifuInstruction = availableWaifuIds?.length
1010
+ ? `Allowed waifuId values: ${availableWaifuIds.join(", ")}. waifuId is the waifu who should remember this observation; it is never a human user name from chat.`
1011
+ : "No waifus are available in this channel; return an empty observations array.";
1012
+ return `You are extracting durable memories from a Discord chat window.
1013
+
1014
+ Each message in the context is tagged with [index: #N] and [timestamp: ISO-8601 UTC], followed by \`DisplayName:\` and the body, optionally followed by [reactions: ...] and [replying to: ...].
1015
+
1016
+ Your only job: scan the window and produce a small list of atomic, durable observations worth remembering. Then call ${OBSERVER_TOOL_NAME} exactly once with an observations array. Do not write normal assistant text. An empty array is allowed and is the correct answer when nothing durable was disclosed.
1017
+
1018
+ What counts as a durable observation (test before emitting): "Would this still be useful to know in a week, with zero memory of this conversation?" If no, drop it.
1019
+
1020
+ Each observation must be:
1021
+ - A single atomic fact, stated independently of the chat. Phrase it as a standalone sentence about a named person, not as a recap of what happened.
1022
+ - Owned by one waifu via waifuId — the waifu who should carry this memory in her prompt going forward. ${waifuInstruction}
1023
+ - Classified by kind: "fact" (stable attribute), "preference" (likes/dislikes), "relationship" (between two named people), "event" (a dated thing that happened), or "commitment" (a promise or future plan).
1024
+ - Scored 1–5 for importance: 1 = trivial flavor, 3 = useful when the waifu next talks to this person, 5 = central to who this person is.
1025
+
1026
+ Do NOT emit narration. Reject strings shaped like:
1027
+ - "Kevin and Mia were talking about cooking." (recap, not a fact)
1028
+ - "The user mentioned a movie." (no specific content)
1029
+ - "Yuki greeted Kevin warmly." (chat event with no durable substance)
1030
+ - "Kevin asked about Yuki's day." (small talk, not a fact about anyone)
1031
+
1032
+ Do emit things like:
1033
+ - "Kevin is allergic to peanuts." (fact)
1034
+ - "Mia prefers green tea over black tea." (preference)
1035
+ - "Kevin and Mia are siblings." (relationship)
1036
+ - "Kevin promised to share his cookbook on Friday." (commitment)
1037
+
1038
+ Importance heuristic: a one-off mention is a 2; a stated preference is a 3; an allergy / hard constraint / family relation is a 4–5. Emotional intensity alone is not importance.
1039
+
1040
+ If the entire window is small talk, banter, or roleplay with no durable facts, return an empty array. That is normal.`;
1041
+ }
1042
+ function librarianSystemPrompt(availableWaifuIds) {
1043
+ const waifuInstruction = availableWaifuIds?.length
1044
+ ? `Allowed memory waifuId values: ${availableWaifuIds.join(", ")}. waifuId is the waifu that receives this memory in her prompt; it is never a human user name.`
1045
+ : "No waifus are available for memory ownership in this channel; use no_change.";
1046
+ return `You are the memory librarian for a Discord waifu bot.
1047
+
1048
+ You receive two JSON blocks in user messages:
1049
+ - \`observations: ...\` — new durable observations extracted from a recent chat window. Each has waifuId, content, importance, and kind.
1050
+ - \`memories: ...\` — a pruned list of existing memories that could plausibly collide with those observations. Each has memoryIndex, waifuId, content, importance. Reference existing memories by memoryIndex.
1051
+
1052
+ Your job: decide, for each observation, whether it is new (add), already covered by an existing memory (no_change), a sharpening of an existing memory (update_memory), one of a group that should merge (merge_memories), or directly contradicts a now-wrong memory (archive_memory + add_memory).
1053
+
1054
+ ${waifuInstruction}
1055
+ All memory edits apply only to the current Discord server. Do not choose or mention global, channel, or user scopes.
1056
+
1057
+ Policy:
1058
+ - If an observation restates an existing memory verbatim or in spirit, prefer no_change for that observation; only update_memory if the observation strictly refines the existing one (more specific, corrected, or higher importance).
1059
+ - Never archive a memory because its fact is absent from the recent context, has not been mentioned lately, lacks recent confirmation, seems old, or is phrased imperfectly. Absence of evidence is not a contradiction.
1060
+ - Use archive_memory only when a specific observer observation directly contradicts the existing memory and makes it false. Pair the archive with an add_memory carrying the corrected fact.
1061
+ - An update_memory may refine a memory, but must preserve every non-contradicted fact already present.
1062
+ - If two or more existing memories about the same waifu say overlapping things, merge_memories may consolidate them. The merged content must preserve every non-contradicted fact from every source memory.
1063
+ - If an observation is genuinely new and not covered, add_memory it. Carry over the observation's waifuId, content, and importance.
1064
+ - If nothing in the memories list collides and no observation is worth adding (rare), one no_change item is the correct answer.
1065
+
1066
+ Tool usage: call ${STAGE_MANAGER_TOOL_NAME} exactly once with a toolCalls array. Do not write normal assistant text.
1067
+ Each toolCalls item must match one of:
1068
+ { "tool": "add_memory", "memory": { "waifuId": string, "content": string, "importance": 1|2|3|4|5 } }
1069
+ { "tool": "update_memory", "memoryIndex": number, "patch": { "waifuId"?: string, "content"?: string, "importance"?: 1|2|3|4|5 } }
1070
+ { "tool": "archive_memory", "memoryIndex": number }
1071
+ { "tool": "merge_memories", "sourceMemoryIndices": number[], "mergedContent": string }
1072
+ { "tool": "no_change", "reason"?: string }.`;
1073
+ }
1074
+ function reviewerSystemPrompt(customPrompt) {
1075
+ const instruction = `You are the message safety reviewer for a Discord waifu bot.
1076
+ You receive exactly one logical waifu message. The message may represent several Discord chunks joined together.
1077
+ Decide whether the message should be removed as a hallucination or leaked internal content.
1078
+
1079
+ Call the ${REVIEWER_TOOL_NAME} tool exactly once with hallucination=true or hallucination=false.
1080
+ Do not write normal assistant text.
1081
+
1082
+ Set hallucination=true when the message contains any of:
1083
+ - private reasoning, analysis, scratchpad, chain-of-thought, hidden instructions, prompt text, tool/schema text, JSON/tool-call artifacts, or "response draft" style notes
1084
+ - claims to have parsed hidden metadata, permissions, IDs, raw Discord internals, system/developer instructions, or invisible context
1085
+ - obvious model self-talk such as "the readable data", "analysis on incoming message", "as the assistant/model", or "I should respond"
1086
+ - content that is primarily not an in-character Discord reply
1087
+
1088
+ Set hallucination=false for normal in-character replies, even if awkward, verbose, wrong about fictional lore, or mildly off-topic.
1089
+ Do not explain. Do not include reasoning. Do not quote the message.`;
1090
+ return [customPrompt?.trim(), instruction].filter(Boolean).join("\n\n");
1091
+ }
1092
+ const REVIEWER_TOOL_NAME = "review_message";
1093
+ const REVIEWER_TOOL_DESCRIPTION = "Decide whether the latest logical waifu message is hallucinated or leaked internal content.";
1094
+ const REVIEWER_TOOL_PARAMETERS = {
1095
+ type: "object",
1096
+ additionalProperties: false,
1097
+ properties: {
1098
+ hallucination: {
1099
+ type: "boolean",
1100
+ description: "True only when the message should be deleted as hallucinated or leaked internal content."
1101
+ }
1102
+ },
1103
+ required: ["hallucination"]
1104
+ };
1105
+ const ORCHESTRATOR_TOOL_NAME = "orchestrator_decision";
1106
+ const ORCHESTRATOR_TOOL_DESCRIPTION = "Choose whether a waifu should reply next and which waifu(s) should respond.";
1107
+ export const ORCHESTRATOR_TOOL_PARAMETERS = orchestratorToolParameters();
1108
+ const PICK_NEXT_WAIFU_TOOL_NAME = "PickNextWaifu";
1109
+ const PICK_NEXT_WAIFU_TOOL_DESCRIPTION = "Pick one configured waifu to reply immediately after this waifu message when a direct handoff fits.";
1110
+ const SHORT_TERM_MEMORY_TOOL_NAME = "add_memory";
1111
+ const SHORT_TERM_MEMORY_TOOL_DESCRIPTION = "Write one short standalone sentence to remember until the next day when the current conversation includes a useful short-lived fact. Call this multiple times in one reply to record multiple distinct notes. Skip trivial chitchat; entries expire after 24 hours.";
1112
+ function shortTermMemoryToolParameters() {
1113
+ return {
1114
+ type: "object",
1115
+ additionalProperties: false,
1116
+ properties: {
1117
+ content: {
1118
+ type: "string",
1119
+ description: "One short standalone sentence about the current conversation state (a stated time, a plan, a name to follow up on)."
1120
+ }
1121
+ },
1122
+ required: ["content"]
1123
+ };
1124
+ }
1125
+ function orchestratorToolParameters(availableWaifuIds, replyRequired = false) {
1126
+ const waifuIds = [...new Set((availableWaifuIds ?? []).filter(Boolean))];
1127
+ const waifuIdSchema = {
1128
+ type: "string",
1129
+ description: waifuIds.length
1130
+ ? `Must be one of the configured waifu ids: ${waifuIds.join(", ")}.`
1131
+ : "Configured waifu id."
1132
+ };
1133
+ if (waifuIds.length) {
1134
+ waifuIdSchema.enum = waifuIds;
1135
+ }
1136
+ return {
1137
+ type: "object",
1138
+ additionalProperties: false,
1139
+ properties: {
1140
+ action: {
1141
+ type: "string",
1142
+ enum: replyRequired ? ["reply"] : ["reply", "no_reply"],
1143
+ description: replyRequired
1144
+ ? "\"reply\" is required for this manual run."
1145
+ : "\"reply\" when at least one waifu should answer; \"no_reply\" when nobody should speak now."
1146
+ },
1147
+ respondingWaifus: {
1148
+ type: "array",
1149
+ description: "Ordered list of waifus that will reply, in send order. Must be non-empty when action is \"reply\" and must be empty when action is \"no_reply\". Waifus speak one after the other; any incoming chat message cancels the rest of the chain.",
1150
+ items: {
1151
+ type: "object",
1152
+ additionalProperties: false,
1153
+ properties: {
1154
+ waifuId: waifuIdSchema,
1155
+ delaySeconds: {
1156
+ type: "number",
1157
+ minimum: 0,
1158
+ maximum: MAX_WAIFU_DELAY_SECONDS,
1159
+ description: `Realistic reading/typing delay before this waifu starts replying, in seconds. 0 means start immediately; maximum is ${MAX_WAIFU_DELAY_SECONDS}.`
1160
+ },
1161
+ replyStyle: {
1162
+ type: "string",
1163
+ enum: [...REPLY_STYLE_VALUES],
1164
+ description: "Soft hint for this reply's length and tone: \"normal\" is the default, \"short\" is one terse line, \"long\" leans toward a slightly longer reply, \"sleepy\" sounds tired/low-energy."
1165
+ },
1166
+ repleyToMessageIndex: {
1167
+ anyOf: [{ type: "integer", minimum: 1 }, { type: "null" }],
1168
+ description: "Optional #N context index to reply to; only set when intentionally anchoring to a specific older message. Otherwise null."
1169
+ },
1170
+ sceneDirection: {
1171
+ anyOf: [{ type: "string" }, { type: "null" }],
1172
+ description: "Optional one-line invisible direction shaping this waifu's next message. Use when the persona alone won't carry the beat. Null when no special steering is needed."
1173
+ }
1174
+ },
1175
+ required: ["waifuId", "delaySeconds", "replyStyle", "repleyToMessageIndex", "sceneDirection"]
1176
+ }
1177
+ },
1178
+ retriggerAfterSeconds: {
1179
+ anyOf: [
1180
+ { type: "number", minimum: RETRIGGER_MIN_SECONDS, maximum: RETRIGGER_MAX_SECONDS },
1181
+ { type: "null" }
1182
+ ],
1183
+ description: `Required when action is \"no_reply\": seconds to wait before the orchestrator re-evaluates the room (${RETRIGGER_MIN_SECONDS}..${RETRIGGER_MAX_SECONDS}). Must be null when action is \"reply\".`
1184
+ },
1185
+ reasoning: {
1186
+ type: "string",
1187
+ description: "Brief operational reason for this decision."
1188
+ }
1189
+ },
1190
+ required: ["action", "respondingWaifus", "retriggerAfterSeconds", "reasoning"]
1191
+ };
1192
+ }
1193
+ function pickNextWaifuToolParameters(availableWaifuIds) {
1194
+ const waifuIds = [...new Set((availableWaifuIds ?? []).filter(Boolean))];
1195
+ const waifuIdSchema = {
1196
+ type: "string",
1197
+ description: waifuIds.length
1198
+ ? `Must be one of these configured waifu ids: ${waifuIds.join(", ")}.`
1199
+ : "Configured waifu id."
1200
+ };
1201
+ if (waifuIds.length) {
1202
+ waifuIdSchema.enum = waifuIds;
1203
+ }
1204
+ return {
1205
+ type: "object",
1206
+ additionalProperties: false,
1207
+ properties: {
1208
+ waifuId: waifuIdSchema
1209
+ },
1210
+ required: ["waifuId"]
1211
+ };
1212
+ }
1213
+ const STAGE_MANAGER_TOOL_NAME = "manage_memories";
1214
+ const STAGE_MANAGER_TOOL_DESCRIPTION = "Return the complete set of memory edits needed for the current Discord context.";
1215
+ export const STAGE_MANAGER_TOOL_PARAMETERS = stageManagerToolParameters();
1216
+ const OBSERVER_TOOL_NAME = "record_observations";
1217
+ const OBSERVER_TOOL_DESCRIPTION = "Return atomic, durable observations extracted from the chat window. An empty array means nothing durable was disclosed.";
1218
+ export const OBSERVER_TOOL_PARAMETERS = observerToolParameters();
1219
+ function observerToolParameters(availableWaifuIds) {
1220
+ const waifuIds = [...new Set((availableWaifuIds ?? []).filter(Boolean))];
1221
+ const waifuIdSchema = {
1222
+ type: "string",
1223
+ description: waifuIds.length
1224
+ ? `Must be one of the configured waifu ids: ${waifuIds.join(", ")}. This is the memory owner, not a human user.`
1225
+ : "Configured waifu id."
1226
+ };
1227
+ if (waifuIds.length) {
1228
+ waifuIdSchema.enum = waifuIds;
1229
+ }
1230
+ return {
1231
+ type: "object",
1232
+ additionalProperties: false,
1233
+ properties: {
1234
+ observations: {
1235
+ type: "array",
1236
+ description: "Durable observations to record. Empty array is valid and means nothing durable was disclosed.",
1237
+ items: {
1238
+ type: "object",
1239
+ additionalProperties: false,
1240
+ required: ["waifuId", "content", "importance", "kind"],
1241
+ properties: {
1242
+ waifuId: waifuIdSchema,
1243
+ content: { type: "string", description: "Atomic standalone fact, not a recap of chat events." },
1244
+ importance: { type: "integer", enum: [1, 2, 3, 4, 5] },
1245
+ kind: { type: "string", enum: [...OBSERVATION_KINDS] }
1246
+ }
1247
+ }
1248
+ }
1249
+ },
1250
+ required: ["observations"]
1251
+ };
1252
+ }
1253
+ function stageManagerToolParameters(availableWaifuIds) {
1254
+ const waifuIds = [...new Set((availableWaifuIds ?? []).filter(Boolean))];
1255
+ const waifuIdSchema = {
1256
+ type: "string",
1257
+ description: waifuIds.length
1258
+ ? `Must be one of the configured waifu ids: ${waifuIds.join(", ")}. This is the memory owner, not a human user.`
1259
+ : "Configured waifu id."
1260
+ };
1261
+ if (waifuIds.length) {
1262
+ waifuIdSchema.enum = waifuIds;
1263
+ }
1264
+ return {
1265
+ type: "object",
1266
+ additionalProperties: false,
1267
+ properties: {
1268
+ toolCalls: {
1269
+ type: "array",
1270
+ description: "Memory edit operations to apply. Use one no_change item when no edit is needed.",
1271
+ minItems: 1,
1272
+ items: {
1273
+ type: "object",
1274
+ additionalProperties: false,
1275
+ properties: {
1276
+ tool: {
1277
+ type: "string",
1278
+ enum: ["add_memory", "update_memory", "archive_memory", "merge_memories", "no_change"]
1279
+ },
1280
+ memory: {
1281
+ type: "object",
1282
+ description: "Required when tool is add_memory.",
1283
+ additionalProperties: false,
1284
+ properties: {
1285
+ waifuId: waifuIdSchema,
1286
+ content: { type: "string" },
1287
+ importance: { type: "integer", enum: [1, 2, 3, 4, 5] }
1288
+ },
1289
+ required: ["waifuId", "content", "importance"]
1290
+ },
1291
+ memoryIndex: {
1292
+ type: "integer",
1293
+ minimum: 1,
1294
+ description: "Required for update_memory or archive_memory."
1295
+ },
1296
+ patch: {
1297
+ type: "object",
1298
+ description: "Required when tool is update_memory.",
1299
+ additionalProperties: false,
1300
+ properties: {
1301
+ waifuId: waifuIdSchema,
1302
+ content: { type: "string" },
1303
+ importance: { type: "integer", enum: [1, 2, 3, 4, 5] }
1304
+ }
1305
+ },
1306
+ sourceMemoryIndices: {
1307
+ type: "array",
1308
+ description: "Required when tool is merge_memories.",
1309
+ minItems: 2,
1310
+ items: { type: "integer", minimum: 1 }
1311
+ },
1312
+ mergedContent: {
1313
+ type: "string",
1314
+ description: "Required when tool is merge_memories."
1315
+ },
1316
+ reason: {
1317
+ type: "string",
1318
+ description: "Optional explanation for no_change."
1319
+ }
1320
+ },
1321
+ required: ["tool"]
1322
+ }
1323
+ }
1324
+ },
1325
+ required: ["toolCalls"]
1326
+ };
1327
+ }
1328
+ function flatStageManagerToolParameters(availableWaifuIds) {
1329
+ const waifuIds = [...new Set((availableWaifuIds ?? []).filter(Boolean))];
1330
+ const waifuIdSchema = {
1331
+ type: "string",
1332
+ description: waifuIds.length
1333
+ ? `Configured waifu id; must be one of: ${waifuIds.join(", ")}.`
1334
+ : "Configured waifu id."
1335
+ };
1336
+ if (waifuIds.length) {
1337
+ waifuIdSchema.enum = waifuIds;
1338
+ }
1339
+ return {
1340
+ type: "object",
1341
+ properties: {
1342
+ toolCalls: {
1343
+ type: "array",
1344
+ description: "Memory edit operations to apply. Use one no_change item when no edit is needed.",
1345
+ minItems: 1,
1346
+ items: {
1347
+ type: "object",
1348
+ properties: {
1349
+ tool: {
1350
+ type: "string",
1351
+ enum: ["add_memory", "update_memory", "archive_memory", "merge_memories", "no_change"]
1352
+ },
1353
+ waifuId: {
1354
+ ...waifuIdSchema,
1355
+ description: "Required for add_memory. Optional for update_memory."
1356
+ },
1357
+ content: {
1358
+ type: "string",
1359
+ description: "Memory content for add_memory/update_memory, or the merged content for merge_memories."
1360
+ },
1361
+ importance: {
1362
+ type: "integer",
1363
+ enum: [1, 2, 3, 4, 5],
1364
+ description: "Required for add_memory. Optional for update_memory."
1365
+ },
1366
+ memoryIndex: {
1367
+ type: "integer",
1368
+ minimum: 1,
1369
+ description: "Required for update_memory or archive_memory."
1370
+ },
1371
+ sourceMemoryIndices: {
1372
+ type: "array",
1373
+ description: "Required for merge_memories.",
1374
+ minItems: 2,
1375
+ items: { type: "integer", minimum: 1 }
1376
+ },
1377
+ reason: {
1378
+ type: "string",
1379
+ description: "Optional explanation for no_change."
1380
+ }
1381
+ },
1382
+ required: ["tool"]
1383
+ }
1384
+ }
1385
+ },
1386
+ required: ["toolCalls"]
1387
+ };
1388
+ }
1389
+ function openAiChatOrchestratorTool(availableWaifuIds, replyRequired = false) {
1390
+ return openAiChatTool(ORCHESTRATOR_TOOL_NAME, ORCHESTRATOR_TOOL_DESCRIPTION, orchestratorToolParameters(availableWaifuIds, replyRequired));
1391
+ }
1392
+ function openAiResponsesOrchestratorTool(availableWaifuIds, replyRequired = false) {
1393
+ return openAiResponsesTool(ORCHESTRATOR_TOOL_NAME, ORCHESTRATOR_TOOL_DESCRIPTION, orchestratorToolParameters(availableWaifuIds, replyRequired));
1394
+ }
1395
+ function anthropicOrchestratorTool(availableWaifuIds, replyRequired = false) {
1396
+ return anthropicTool(ORCHESTRATOR_TOOL_NAME, ORCHESTRATOR_TOOL_DESCRIPTION, orchestratorToolParameters(availableWaifuIds, replyRequired));
1397
+ }
1398
+ function openAiChatPickNextWaifuTool(availableWaifuIds) {
1399
+ return openAiChatTool(PICK_NEXT_WAIFU_TOOL_NAME, PICK_NEXT_WAIFU_TOOL_DESCRIPTION, pickNextWaifuToolParameters(availableWaifuIds));
1400
+ }
1401
+ function openAiResponsesPickNextWaifuTool(availableWaifuIds) {
1402
+ return openAiResponsesTool(PICK_NEXT_WAIFU_TOOL_NAME, PICK_NEXT_WAIFU_TOOL_DESCRIPTION, pickNextWaifuToolParameters(availableWaifuIds));
1403
+ }
1404
+ function anthropicPickNextWaifuTool(availableWaifuIds) {
1405
+ return anthropicTool(PICK_NEXT_WAIFU_TOOL_NAME, PICK_NEXT_WAIFU_TOOL_DESCRIPTION, pickNextWaifuToolParameters(availableWaifuIds));
1406
+ }
1407
+ function openAiChatShortTermMemoryTool() {
1408
+ return openAiChatTool(SHORT_TERM_MEMORY_TOOL_NAME, SHORT_TERM_MEMORY_TOOL_DESCRIPTION, shortTermMemoryToolParameters());
1409
+ }
1410
+ function openAiResponsesShortTermMemoryTool() {
1411
+ return openAiResponsesTool(SHORT_TERM_MEMORY_TOOL_NAME, SHORT_TERM_MEMORY_TOOL_DESCRIPTION, shortTermMemoryToolParameters());
1412
+ }
1413
+ function anthropicShortTermMemoryTool() {
1414
+ return anthropicTool(SHORT_TERM_MEMORY_TOOL_NAME, SHORT_TERM_MEMORY_TOOL_DESCRIPTION, shortTermMemoryToolParameters());
1415
+ }
1416
+ function openAiChatStageManagerTool(availableWaifuIds) {
1417
+ return openAiChatTool(STAGE_MANAGER_TOOL_NAME, STAGE_MANAGER_TOOL_DESCRIPTION, stageManagerToolParameters(availableWaifuIds));
1418
+ }
1419
+ function openAiResponsesStageManagerTool(availableWaifuIds) {
1420
+ return openAiResponsesTool(STAGE_MANAGER_TOOL_NAME, STAGE_MANAGER_TOOL_DESCRIPTION, stageManagerToolParameters(availableWaifuIds));
1421
+ }
1422
+ function anthropicStageManagerTool(availableWaifuIds) {
1423
+ return anthropicTool(STAGE_MANAGER_TOOL_NAME, STAGE_MANAGER_TOOL_DESCRIPTION, stageManagerToolParameters(availableWaifuIds));
1424
+ }
1425
+ function openAiChatObserverTool(availableWaifuIds) {
1426
+ return openAiChatTool(OBSERVER_TOOL_NAME, OBSERVER_TOOL_DESCRIPTION, observerToolParameters(availableWaifuIds));
1427
+ }
1428
+ function openAiResponsesObserverTool(availableWaifuIds) {
1429
+ return openAiResponsesTool(OBSERVER_TOOL_NAME, OBSERVER_TOOL_DESCRIPTION, observerToolParameters(availableWaifuIds));
1430
+ }
1431
+ function anthropicObserverTool(availableWaifuIds) {
1432
+ return anthropicTool(OBSERVER_TOOL_NAME, OBSERVER_TOOL_DESCRIPTION, observerToolParameters(availableWaifuIds));
1433
+ }
1434
+ function openAiChatReviewerTool() {
1435
+ return openAiChatTool(REVIEWER_TOOL_NAME, REVIEWER_TOOL_DESCRIPTION, REVIEWER_TOOL_PARAMETERS);
1436
+ }
1437
+ function openAiResponsesReviewerTool() {
1438
+ return openAiResponsesTool(REVIEWER_TOOL_NAME, REVIEWER_TOOL_DESCRIPTION, REVIEWER_TOOL_PARAMETERS, true);
1439
+ }
1440
+ function anthropicReviewerTool() {
1441
+ return anthropicTool(REVIEWER_TOOL_NAME, REVIEWER_TOOL_DESCRIPTION, REVIEWER_TOOL_PARAMETERS);
1442
+ }
1443
+ function shouldExposePickNextWaifuTool(model, request) {
1444
+ return Boolean(model.supportsTools &&
1445
+ request.pickNextWaifuToolEnabled &&
1446
+ (request.availableWaifuIds?.length ?? 0) > 0);
1447
+ }
1448
+ function shouldExposeShortTermMemoryTool(model, request) {
1449
+ return Boolean(model.supportsTools && request.shortTermMemoryToolEnabled);
1450
+ }
1451
+ function openAiChatPickNextWaifuToolPayload(model, request) {
1452
+ if (!shouldExposePickNextWaifuTool(model, request))
1453
+ return {};
1454
+ if (model.providerId === "deepseek") {
1455
+ return {
1456
+ tools: [openAiChatPickNextWaifuTool(request.availableWaifuIds)]
1457
+ };
1458
+ }
1459
+ return {
1460
+ tools: [openAiChatPickNextWaifuTool(request.availableWaifuIds)],
1461
+ tool_choice: "auto"
1462
+ };
1463
+ }
1464
+ function openAiResponsesPickNextWaifuToolPayload(model, request) {
1465
+ if (!shouldExposePickNextWaifuTool(model, request))
1466
+ return {};
1467
+ return {
1468
+ tools: [openAiResponsesPickNextWaifuTool(request.availableWaifuIds)],
1469
+ tool_choice: "auto"
1470
+ };
1471
+ }
1472
+ function anthropicPickNextWaifuToolPayload(model, request) {
1473
+ if (!shouldExposePickNextWaifuTool(model, request))
1474
+ return {};
1475
+ return {
1476
+ tools: [anthropicPickNextWaifuTool(request.availableWaifuIds)],
1477
+ tool_choice: { type: "auto" }
1478
+ };
1479
+ }
1480
+ function openAiChatShortTermMemoryToolPayload(model, request) {
1481
+ if (!shouldExposeShortTermMemoryTool(model, request))
1482
+ return {};
1483
+ if (model.providerId === "deepseek") {
1484
+ return { tools: [openAiChatShortTermMemoryTool()] };
1485
+ }
1486
+ return { tools: [openAiChatShortTermMemoryTool()], tool_choice: "auto" };
1487
+ }
1488
+ function openAiResponsesShortTermMemoryToolPayload(model, request) {
1489
+ if (!shouldExposeShortTermMemoryTool(model, request))
1490
+ return {};
1491
+ return { tools: [openAiResponsesShortTermMemoryTool()], tool_choice: "auto" };
1492
+ }
1493
+ function anthropicShortTermMemoryToolPayload(model, request) {
1494
+ if (!shouldExposeShortTermMemoryTool(model, request))
1495
+ return {};
1496
+ return { tools: [anthropicShortTermMemoryTool()], tool_choice: { type: "auto" } };
1497
+ }
1498
+ function openAiChatWaifuToolsPayload(model, request) {
1499
+ const pick = openAiChatPickNextWaifuToolPayload(model, request);
1500
+ const mem = openAiChatShortTermMemoryToolPayload(model, request);
1501
+ const tools = [...(pick.tools ?? []), ...(mem.tools ?? [])];
1502
+ if (!tools.length)
1503
+ return {};
1504
+ const tool_choice = pick.tool_choice ?? mem.tool_choice;
1505
+ return tool_choice ? { tools, tool_choice } : { tools };
1506
+ }
1507
+ function openAiResponsesWaifuToolsPayload(model, request) {
1508
+ const pick = openAiResponsesPickNextWaifuToolPayload(model, request);
1509
+ const mem = openAiResponsesShortTermMemoryToolPayload(model, request);
1510
+ const tools = [...(pick.tools ?? []), ...(mem.tools ?? [])];
1511
+ if (!tools.length)
1512
+ return {};
1513
+ const tool_choice = pick.tool_choice ?? mem.tool_choice;
1514
+ return tool_choice ? { tools, tool_choice } : { tools };
1515
+ }
1516
+ function anthropicWaifuToolsPayload(model, request) {
1517
+ const pick = anthropicPickNextWaifuToolPayload(model, request);
1518
+ const mem = anthropicShortTermMemoryToolPayload(model, request);
1519
+ const tools = [...(pick.tools ?? []), ...(mem.tools ?? [])];
1520
+ if (!tools.length)
1521
+ return {};
1522
+ const tool_choice = pick.tool_choice ?? mem.tool_choice;
1523
+ return tool_choice ? { tools, tool_choice } : { tools };
1524
+ }
1525
+ function openAiChatTool(name, description, parameters) {
1526
+ return {
1527
+ type: "function",
1528
+ function: {
1529
+ name,
1530
+ description,
1531
+ parameters
1532
+ }
1533
+ };
1534
+ }
1535
+ function openAiResponsesTool(name, description, parameters, strict = false) {
1536
+ return {
1537
+ type: "function",
1538
+ name,
1539
+ description,
1540
+ parameters,
1541
+ ...(strict ? { strict: true } : {})
1542
+ };
1543
+ }
1544
+ function anthropicTool(name, description, inputSchema) {
1545
+ return {
1546
+ name,
1547
+ description,
1548
+ input_schema: inputSchema
1549
+ };
1550
+ }
1551
+ const ImportanceSchema = z.union([
1552
+ z.literal(1),
1553
+ z.literal(2),
1554
+ z.literal(3),
1555
+ z.literal(4),
1556
+ z.literal(5)
1557
+ ]);
1558
+ const RawImportanceSchema = z.preprocess((value) => {
1559
+ if (typeof value === "string" && /^[1-5]$/.test(value)) {
1560
+ return Number(value);
1561
+ }
1562
+ return value;
1563
+ }, ImportanceSchema);
1564
+ const RawStageManagerObservationSchema = z.object({
1565
+ waifuId: z.string().min(1),
1566
+ content: z.string().min(1),
1567
+ importance: RawImportanceSchema,
1568
+ kind: z.enum(OBSERVATION_KINDS)
1569
+ });
1570
+ const RawRespondingWaifuSchema = z.object({
1571
+ waifuId: z.string().min(1),
1572
+ delaySeconds: z.number().min(0),
1573
+ replyStyle: ReplyStyleSchema,
1574
+ repleyToMessageIndex: z.union([z.number().int().min(1), z.null()]).optional(),
1575
+ sceneDirection: z.union([z.string().min(1), z.null()]).optional()
1576
+ });
1577
+ const PickNextWaifuCallSchema = z.object({
1578
+ waifuId: z.string().min(1)
1579
+ });
1580
+ const RawOrchestratorDecisionSchema = z.object({
1581
+ action: OrchestratorActionSchema,
1582
+ respondingWaifus: z.array(RawRespondingWaifuSchema).default([]),
1583
+ retriggerAfterSeconds: z
1584
+ .union([z.number().min(RETRIGGER_MIN_SECONDS).max(RETRIGGER_MAX_SECONDS), z.null()])
1585
+ .optional(),
1586
+ reasoning: z.string().min(1)
1587
+ });
1588
+ const RawStageManagerPatchSchema = z.object({
1589
+ waifuId: z.string().min(1).optional(),
1590
+ content: z.string().min(1).optional(),
1591
+ importance: RawImportanceSchema.optional()
1592
+ });
1593
+ const RawStageManagerToolCallSchema = z.discriminatedUnion("tool", [
1594
+ z.object({
1595
+ tool: z.literal("add_memory"),
1596
+ memory: z.object({
1597
+ waifuId: z.string().min(1),
1598
+ content: z.string().min(1),
1599
+ importance: RawImportanceSchema
1600
+ })
1601
+ }),
1602
+ z.object({
1603
+ tool: z.literal("update_memory"),
1604
+ memoryIndex: z.number().int().min(1),
1605
+ patch: RawStageManagerPatchSchema
1606
+ }),
1607
+ z.object({
1608
+ tool: z.literal("archive_memory"),
1609
+ memoryIndex: z.number().int().min(1)
1610
+ }),
1611
+ z.object({
1612
+ tool: z.literal("merge_memories"),
1613
+ sourceMemoryIndices: z.array(z.number().int().min(1)).min(2),
1614
+ mergedContent: z.string().min(1)
1615
+ }),
1616
+ z.object({
1617
+ tool: z.literal("no_change"),
1618
+ reason: z.string().optional()
1619
+ })
1620
+ ]);
1621
+ const RawFlatStageManagerToolCallSchema = z.object({
1622
+ tool: z.enum(["add_memory", "update_memory", "archive_memory", "merge_memories", "no_change"]),
1623
+ waifuId: z.string().min(1).optional(),
1624
+ content: z.string().min(1).optional(),
1625
+ importance: RawImportanceSchema.optional(),
1626
+ memoryIndex: z.number().int().min(1).optional(),
1627
+ sourceMemoryIndices: z.array(z.number().int().min(1)).min(2).optional(),
1628
+ mergedContent: z.string().min(1).optional(),
1629
+ reason: z.string().optional()
1630
+ });
1631
+ function parseDecision(text, indexToId, replyRequired = false) {
1632
+ try {
1633
+ const parsed = JSON.parse(stripCodeFence(text));
1634
+ const raw = RawOrchestratorDecisionSchema.parse(parsed);
1635
+ if (replyRequired && raw.action !== "reply") {
1636
+ throw new Error("Manual /run requires action=reply.");
1637
+ }
1638
+ return OrchestratorDecisionSchema.parse({
1639
+ action: raw.action,
1640
+ respondingWaifus: raw.respondingWaifus.map((entry) => ({
1641
+ waifuId: entry.waifuId,
1642
+ delaySeconds: entry.delaySeconds,
1643
+ replyStyle: entry.replyStyle,
1644
+ replyToMessageId: replyToMessageIdForIndex(entry.repleyToMessageIndex, indexToId),
1645
+ sceneDirection: entry.sceneDirection ?? undefined
1646
+ })),
1647
+ retriggerAfterSeconds: raw.retriggerAfterSeconds === null ? undefined : raw.retriggerAfterSeconds,
1648
+ reasoning: raw.reasoning
1649
+ });
1650
+ }
1651
+ catch (error) {
1652
+ throw new ProviderPipelineError("Provider did not return a valid orchestrator decision.", {
1653
+ text,
1654
+ error: error instanceof Error ? error.message : String(error)
1655
+ });
1656
+ }
1657
+ }
1658
+ function replyToMessageIdForIndex(repleyToMessageIndex, indexToId) {
1659
+ if (repleyToMessageIndex === null || repleyToMessageIndex === undefined) {
1660
+ return undefined;
1661
+ }
1662
+ const messageId = indexToId.get(repleyToMessageIndex);
1663
+ if (!messageId) {
1664
+ throw new Error(`repleyToMessageIndex #${repleyToMessageIndex} does not exist in the provided context.`);
1665
+ }
1666
+ return messageId;
1667
+ }
1668
+ function parseStageManagerCalls(text) {
1669
+ try {
1670
+ const parsed = JSON.parse(stripCodeFence(text));
1671
+ const calls = Array.isArray(parsed) ? parsed : (parsed.toolCalls ?? parsed.calls ?? []);
1672
+ if (!Array.isArray(calls)) {
1673
+ throw new Error("stage-manager response did not contain a toolCalls array.");
1674
+ }
1675
+ const validCalls = [];
1676
+ const invalidCalls = [];
1677
+ calls.forEach((call, index) => {
1678
+ try {
1679
+ validCalls.push(normalizeStageManagerToolCall(call));
1680
+ }
1681
+ catch (error) {
1682
+ invalidCalls.push(`#${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
1683
+ }
1684
+ });
1685
+ if (validCalls.length > 0) {
1686
+ return validCalls;
1687
+ }
1688
+ throw new Error(invalidCalls.length
1689
+ ? `No valid stage-manager tool calls. Invalid calls: ${invalidCalls.join("; ")}`
1690
+ : "Provider returned no stage-manager tool calls.");
1691
+ }
1692
+ catch (error) {
1693
+ throw new ProviderPipelineError("Provider did not return valid stage-manager tool calls.", {
1694
+ text,
1695
+ error: error instanceof Error ? error.message : String(error)
1696
+ });
1697
+ }
1698
+ }
1699
+ function normalizeStageManagerToolCall(call) {
1700
+ const nested = RawStageManagerToolCallSchema.safeParse(call);
1701
+ if (nested.success) {
1702
+ return StageManagerToolCallSchema.parse(nested.data);
1703
+ }
1704
+ const raw = RawFlatStageManagerToolCallSchema.parse(call);
1705
+ switch (raw.tool) {
1706
+ case "add_memory":
1707
+ return StageManagerToolCallSchema.parse({
1708
+ tool: raw.tool,
1709
+ memory: {
1710
+ waifuId: raw.waifuId,
1711
+ content: raw.content,
1712
+ importance: raw.importance
1713
+ }
1714
+ });
1715
+ case "update_memory":
1716
+ return StageManagerToolCallSchema.parse({
1717
+ tool: raw.tool,
1718
+ memoryIndex: raw.memoryIndex,
1719
+ patch: stripUndefined({
1720
+ waifuId: raw.waifuId,
1721
+ content: raw.content,
1722
+ importance: raw.importance
1723
+ })
1724
+ });
1725
+ case "archive_memory":
1726
+ return StageManagerToolCallSchema.parse({
1727
+ tool: raw.tool,
1728
+ memoryIndex: raw.memoryIndex
1729
+ });
1730
+ case "merge_memories":
1731
+ return StageManagerToolCallSchema.parse({
1732
+ tool: raw.tool,
1733
+ sourceMemoryIndices: raw.sourceMemoryIndices,
1734
+ mergedContent: raw.mergedContent ?? raw.content
1735
+ });
1736
+ case "no_change":
1737
+ return StageManagerToolCallSchema.parse({
1738
+ tool: raw.tool,
1739
+ reason: raw.reason
1740
+ });
1741
+ }
1742
+ }
1743
+ function parseStageManagerObservations(text) {
1744
+ try {
1745
+ const parsed = JSON.parse(stripCodeFence(text));
1746
+ const raw = Array.isArray(parsed) ? parsed : (parsed.observations ?? []);
1747
+ return raw.map((item) => StageManagerObservationSchema.parse(RawStageManagerObservationSchema.parse(item)));
1748
+ }
1749
+ catch (error) {
1750
+ throw new ProviderPipelineError("Provider did not return valid stage-manager observations.", {
1751
+ text,
1752
+ error: error instanceof Error ? error.message : String(error)
1753
+ });
1754
+ }
1755
+ }
1756
+ function parseReviewerDecision(text) {
1757
+ try {
1758
+ const parsed = JSON.parse(stripCodeFence(text));
1759
+ return ReviewerDecisionSchema.parse(parsed);
1760
+ }
1761
+ catch (error) {
1762
+ throw new ProviderPipelineError("Provider did not return a valid reviewer decision.", {
1763
+ text,
1764
+ error: error instanceof Error ? error.message : String(error)
1765
+ });
1766
+ }
1767
+ }
1768
+ function stripCodeFence(text) {
1769
+ return text
1770
+ .trim()
1771
+ .replace(/^```(?:json)?\s*/i, "")
1772
+ .replace(/\s*```$/i, "");
1773
+ }
1774
+ function extractOpenAiChatText(json) {
1775
+ const parsed = json;
1776
+ return parsed.choices?.[0]?.message?.content ?? "";
1777
+ }
1778
+ function extractOpenAiChatWaifuResult(json, availableWaifuIds) {
1779
+ const parsed = json;
1780
+ const message = parsed.choices?.[0]?.message;
1781
+ const toolCall = message?.tool_calls?.find((call) => call.function?.name === PICK_NEXT_WAIFU_TOOL_NAME);
1782
+ const shortTermMemoryEntries = (message?.tool_calls ?? [])
1783
+ .filter((call) => call.function?.name === SHORT_TERM_MEMORY_TOOL_NAME)
1784
+ .map((call) => parseShortTermMemoryArguments(call.function?.arguments))
1785
+ .filter((entry) => Boolean(entry));
1786
+ return {
1787
+ content: message?.content ?? "",
1788
+ ...parsePickedNextWaifu(toolCall?.function?.arguments, availableWaifuIds),
1789
+ ...(shortTermMemoryEntries.length ? { shortTermMemoryEntries } : {})
1790
+ };
1791
+ }
1792
+ function extractOpenAiChatToolArguments(json, toolName) {
1793
+ const parsed = json;
1794
+ const message = parsed.choices?.[0]?.message;
1795
+ const toolCall = message?.tool_calls?.find((call) => call.function?.name === toolName) ??
1796
+ message?.tool_calls?.[0];
1797
+ const args = toolCall?.function?.arguments;
1798
+ if (typeof args === "string")
1799
+ return args;
1800
+ if (args && typeof args === "object")
1801
+ return JSON.stringify(args);
1802
+ return message?.content ?? "";
1803
+ }
1804
+ function extractOpenAiResponsesText(json) {
1805
+ const parsed = json;
1806
+ if (parsed.output_text)
1807
+ return parsed.output_text;
1808
+ return (parsed.output
1809
+ ?.flatMap((item) => item.content ?? [])
1810
+ .map((content) => content.text ?? "")
1811
+ .join("") ?? "");
1812
+ }
1813
+ function extractOpenAiResponsesWaifuResult(json, availableWaifuIds) {
1814
+ const parsed = json;
1815
+ const call = parsed.output?.find((item) => item.type === "function_call" && item.name === PICK_NEXT_WAIFU_TOOL_NAME);
1816
+ const shortTermMemoryEntries = (parsed.output ?? [])
1817
+ .filter((item) => item.type === "function_call" && item.name === SHORT_TERM_MEMORY_TOOL_NAME)
1818
+ .map((item) => parseShortTermMemoryArguments(item.arguments))
1819
+ .filter((entry) => Boolean(entry));
1820
+ return {
1821
+ content: extractOpenAiResponsesText(parsed),
1822
+ ...parsePickedNextWaifu(call?.arguments, availableWaifuIds),
1823
+ ...(shortTermMemoryEntries.length ? { shortTermMemoryEntries } : {})
1824
+ };
1825
+ }
1826
+ function extractOpenAiResponsesToolArguments(json, toolName) {
1827
+ const parsed = json;
1828
+ const call = parsed.output?.find((item) => item.type === "function_call" && item.name === toolName) ??
1829
+ parsed.output?.find((item) => item.type === "function_call");
1830
+ if (typeof call?.arguments === "string")
1831
+ return call.arguments;
1832
+ if (call?.arguments && typeof call.arguments === "object")
1833
+ return JSON.stringify(call.arguments);
1834
+ return extractOpenAiResponsesText(parsed) || parsed.output_text || "";
1835
+ }
1836
+ function extractAnthropicText(json) {
1837
+ const parsed = json;
1838
+ return parsed.content?.map((part) => part.text ?? "").join("") ?? "";
1839
+ }
1840
+ function extractAnthropicWaifuResult(json, availableWaifuIds) {
1841
+ const parsed = json;
1842
+ const toolUse = parsed.content?.find((part) => part.type === "tool_use" && part.name === PICK_NEXT_WAIFU_TOOL_NAME);
1843
+ const shortTermMemoryEntries = (parsed.content ?? [])
1844
+ .filter((part) => part.type === "tool_use" && part.name === SHORT_TERM_MEMORY_TOOL_NAME)
1845
+ .map((part) => parseShortTermMemoryArguments(part.input))
1846
+ .filter((entry) => Boolean(entry));
1847
+ return {
1848
+ content: extractAnthropicText(parsed),
1849
+ ...parsePickedNextWaifu(toolUse?.input, availableWaifuIds),
1850
+ ...(shortTermMemoryEntries.length ? { shortTermMemoryEntries } : {})
1851
+ };
1852
+ }
1853
+ function extractAnthropicToolArguments(json, toolName) {
1854
+ const parsed = json;
1855
+ const toolUse = parsed.content?.find((part) => part.type === "tool_use" && part.name === toolName) ??
1856
+ parsed.content?.find((part) => part.type === "tool_use");
1857
+ if (typeof toolUse?.input === "string")
1858
+ return toolUse.input;
1859
+ if (toolUse?.input && typeof toolUse.input === "object")
1860
+ return JSON.stringify(toolUse.input);
1861
+ return extractAnthropicText(parsed);
1862
+ }
1863
+ const ShortTermMemoryCallSchema = z.object({
1864
+ content: z.string().min(1)
1865
+ });
1866
+ function parseShortTermMemoryArguments(argumentsValue) {
1867
+ if (argumentsValue === undefined || argumentsValue === null)
1868
+ return undefined;
1869
+ try {
1870
+ const parsed = typeof argumentsValue === "string"
1871
+ ? JSON.parse(stripCodeFence(argumentsValue))
1872
+ : argumentsValue;
1873
+ const call = ShortTermMemoryCallSchema.parse(parsed);
1874
+ return call.content.trim() || undefined;
1875
+ }
1876
+ catch {
1877
+ return undefined;
1878
+ }
1879
+ }
1880
+ function parsePickedNextWaifu(argumentsValue, availableWaifuIds) {
1881
+ if (argumentsValue === undefined || argumentsValue === null)
1882
+ return {};
1883
+ try {
1884
+ const parsed = typeof argumentsValue === "string"
1885
+ ? JSON.parse(stripCodeFence(argumentsValue))
1886
+ : argumentsValue;
1887
+ const call = PickNextWaifuCallSchema.parse(parsed);
1888
+ const allowed = new Set(availableWaifuIds ?? []);
1889
+ if (allowed.size > 0 && !allowed.has(call.waifuId)) {
1890
+ return {
1891
+ rejectedPickNextWaifu: {
1892
+ reason: "unavailable_waifu",
1893
+ waifuId: call.waifuId
1894
+ }
1895
+ };
1896
+ }
1897
+ return { pickedNextWaifuId: call.waifuId };
1898
+ }
1899
+ catch {
1900
+ return {
1901
+ rejectedPickNextWaifu: {
1902
+ reason: "malformed"
1903
+ }
1904
+ };
1905
+ }
1906
+ }
1907
+ function validateMaxOutputTokens(model, maxOutputTokens) {
1908
+ if (maxOutputTokens === undefined || model.maxOutputTokens === undefined)
1909
+ return;
1910
+ if (maxOutputTokens > model.maxOutputTokens) {
1911
+ throw new ProviderPipelineError(`${model.modelId} supports at most ${model.maxOutputTokens} output tokens; received ${maxOutputTokens}.`);
1912
+ }
1913
+ }
1914
+ function openAiChatMessagesForModel(model, messages) {
1915
+ const providerSafeMessages = model.providerId === "deepseek"
1916
+ ? messages.map((message) => {
1917
+ if (typeof message.content !== "string")
1918
+ return message;
1919
+ return {
1920
+ ...message,
1921
+ content: shieldMalformedHexEscapes(message.content)
1922
+ };
1923
+ })
1924
+ : messages;
1925
+ if (model.providerId !== "zai" || providerSafeMessages.some((message) => message.role === "user")) {
1926
+ return providerSafeMessages;
1927
+ }
1928
+ const lastIndex = providerSafeMessages.length - 1;
1929
+ return providerSafeMessages.map((message, index) => index === lastIndex ? { ...message, role: "user" } : message);
1930
+ }
1931
+ function shieldMalformedHexEscapes(content) {
1932
+ return content.replace(/(\\+)([uUx])([0-9A-Fa-f]*)/g, (match, slashes, marker, digits) => {
1933
+ const expectedDigits = marker === "U" ? 8 : marker === "u" ? 4 : 2;
1934
+ if (slashes.length % 2 === 0 || digits.length >= expectedDigits) {
1935
+ return match;
1936
+ }
1937
+ return `\\${match}`;
1938
+ });
1939
+ }
1940
+ function openAiChatTemperature(model, temperature) {
1941
+ if (model.providerId === "zai" && temperature !== undefined) {
1942
+ return Math.max(0, Math.min(1, temperature));
1943
+ }
1944
+ return temperature;
1945
+ }
1946
+ function openAiChatForcedToolChoice(model, toolName) {
1947
+ // Z.AI documents tool_choice as an enum with only "auto"; forced OpenAI-style objects return 400.
1948
+ if (model.providerId === "zai")
1949
+ return "auto";
1950
+ return { type: "function", function: { name: toolName } };
1951
+ }
1952
+ function openAiChatTopP(model, topP) {
1953
+ if (model.providerId === "zai" && topP !== undefined && topP < 0.01) {
1954
+ return undefined;
1955
+ }
1956
+ return topP;
1957
+ }
1958
+ function openAiChatStopSequences(model, reasoning, stopSequences) {
1959
+ if (!stopSequences?.length)
1960
+ return undefined;
1961
+ if (isXaiReasoningRequest(model, reasoning))
1962
+ return undefined;
1963
+ if (model.providerId === "zai")
1964
+ return [stopSequences[0]];
1965
+ return stopSequences;
1966
+ }
1967
+ function openAiChatReasoningForForcedTool(model, reasoning) {
1968
+ // DeepSeek V4 defaults to thinking mode; keep forced tool calls in non-thinking mode.
1969
+ if (model.providerId === "deepseek")
1970
+ return { enabled: false };
1971
+ return reasoning;
1972
+ }
1973
+ function openAiChatReasoningForWaifu(model, request) {
1974
+ if (model.providerId !== "deepseek")
1975
+ return request.reasoning;
1976
+ if (shouldExposePickNextWaifuTool(model, request))
1977
+ return { enabled: false };
1978
+ if (request.reasoning?.enabled === true)
1979
+ return request.reasoning;
1980
+ return { enabled: false };
1981
+ }
1982
+ function isXaiReasoningRequest(model, reasoning) {
1983
+ if (model.providerId !== "xai")
1984
+ return false;
1985
+ if (model.modelId === "grok-4.20-0309-reasoning")
1986
+ return true;
1987
+ if (model.modelId === "grok-4.20-0309-non-reasoning")
1988
+ return false;
1989
+ if (model.modelId === "grok-4.3") {
1990
+ return reasoning?.enabled === false || reasoning?.effort === "none" ? false : true;
1991
+ }
1992
+ return false;
1993
+ }
1994
+ function openAiChatSamplingOverrides(model, reasoning) {
1995
+ // DeepSeek thinking mode is incompatible with temperature/top_p/presence_penalty/frequency_penalty.
1996
+ if (model.providerId === "deepseek" && reasoning?.enabled === true) {
1997
+ return { temperature: undefined, top_p: undefined };
1998
+ }
1999
+ return {};
2000
+ }
2001
+ function openAiResponsesSamplingOverrides(model) {
2002
+ // OpenAI gpt-5.x reasoning models reject temperature/top_p with a 400 unsupported_parameter error.
2003
+ if (model.providerId === "openai" && model.reasoningControls.includes("reasoning.effort")) {
2004
+ return { temperature: undefined, top_p: undefined };
2005
+ }
2006
+ return {};
2007
+ }
2008
+ function reasoningFieldsForOpenAiChat(model, reasoning) {
2009
+ if (!reasoning)
2010
+ return {};
2011
+ const controls = new Set(model.reasoningControls);
2012
+ if (model.providerId === "xai") {
2013
+ if (controls.has("reasoning.enabled") && reasoning.enabled === false) {
2014
+ return { reasoning_effort: "none" };
2015
+ }
2016
+ if (controls.has("reasoning.effort") && reasoning.effort) {
2017
+ const effort = xaiReasoningEffort(reasoning.effort);
2018
+ return effort ? { reasoning_effort: effort } : {};
2019
+ }
2020
+ return {};
2021
+ }
2022
+ if (model.providerId === "deepseek") {
2023
+ const fields = {};
2024
+ if (controls.has("reasoning.enabled") && reasoning.enabled !== undefined) {
2025
+ fields.thinking = { type: reasoning.enabled ? "enabled" : "disabled" };
2026
+ }
2027
+ if (reasoning.enabled !== false && controls.has("reasoning.effort") && reasoning.effort) {
2028
+ fields.reasoning_effort = deepSeekReasoningEffort(reasoning.effort);
2029
+ }
2030
+ return fields;
2031
+ }
2032
+ if (model.providerId === "zai") {
2033
+ if (controls.has("reasoning.enabled") && reasoning.enabled !== undefined) {
2034
+ return { thinking: { type: reasoning.enabled ? "enabled" : "disabled", clear_thinking: true } };
2035
+ }
2036
+ return {};
2037
+ }
2038
+ return {};
2039
+ }
2040
+ function reasoningFieldsForOpenAiResponses(model, reasoning) {
2041
+ if (!reasoning)
2042
+ return {};
2043
+ const controls = new Set(model.reasoningControls);
2044
+ if (controls.has("reasoning.effort") && reasoning.effort) {
2045
+ const effort = openAiReasoningEffort(reasoning.effort);
2046
+ return effort ? { reasoning: { effort } } : {};
2047
+ }
2048
+ return {};
2049
+ }
2050
+ function xaiReasoningEffort(effort) {
2051
+ if (effort === "none" || effort === "low" || effort === "medium" || effort === "high")
2052
+ return effort;
2053
+ return undefined;
2054
+ }
2055
+ function deepSeekReasoningEffort(effort) {
2056
+ return effort === "max" ? "max" : "high";
2057
+ }
2058
+ function openAiReasoningEffort(effort) {
2059
+ if (effort === "none" ||
2060
+ effort === "minimal" ||
2061
+ effort === "low" ||
2062
+ effort === "medium" ||
2063
+ effort === "high" ||
2064
+ effort === "xhigh") {
2065
+ return effort;
2066
+ }
2067
+ return undefined;
2068
+ }
2069
+ function anthropicDefaultMaxTokens(model, reasoning) {
2070
+ if (model.modelId.startsWith("claude-haiku-4-5") && reasoning?.enabled === true) {
2071
+ return 2048;
2072
+ }
2073
+ return 1024;
2074
+ }
2075
+ function anthropicThinkingPayload(model, reasoning, maxTokens) {
2076
+ const modelId = model.modelId;
2077
+ // Opus 4.7: adaptive is the only thinking-on mode, but it must be requested explicitly.
2078
+ if (modelId === "claude-opus-4-7") {
2079
+ if (reasoning?.enabled === false)
2080
+ return undefined;
2081
+ if (reasoning?.enabled === true || reasoning?.effort)
2082
+ return { type: "adaptive" };
2083
+ return undefined;
2084
+ }
2085
+ // Sonnet 4.6: adaptive recommended; can be disabled.
2086
+ if (modelId === "claude-sonnet-4-6") {
2087
+ if (reasoning?.enabled === false)
2088
+ return { type: "disabled" };
2089
+ if (reasoning?.enabled === true || reasoning?.effort) {
2090
+ return { type: "adaptive" };
2091
+ }
2092
+ return undefined;
2093
+ }
2094
+ // Haiku 4.5 (and other manual-only models): manual enabled/disabled.
2095
+ if (modelId.startsWith("claude-haiku-4-5")) {
2096
+ if (reasoning?.enabled === false)
2097
+ return { type: "disabled" };
2098
+ if (!reasoning?.enabled)
2099
+ return undefined;
2100
+ const requested = reasoning.budgetTokens && reasoning.budgetTokens > 0
2101
+ ? reasoning.budgetTokens
2102
+ : 1024;
2103
+ const maxBudget = maxTokens - 1;
2104
+ if (maxBudget < 1024)
2105
+ return undefined;
2106
+ const budget = Math.max(1024, Math.min(requested, maxBudget));
2107
+ return { type: "enabled", budget_tokens: budget };
2108
+ }
2109
+ return undefined;
2110
+ }
2111
+ function anthropicThinkingConstrainsSampling(thinking) {
2112
+ return thinking?.type === "enabled" || thinking?.type === "adaptive";
2113
+ }
2114
+ function anthropicOutputConfig(model, reasoning, thinking) {
2115
+ if (thinking?.type !== "adaptive")
2116
+ return {};
2117
+ const effort = anthropicEffort(model, reasoning?.effort);
2118
+ return effort ? { output_config: { effort } } : {};
2119
+ }
2120
+ function anthropicEffort(model, effort) {
2121
+ if (effort === "low" || effort === "medium" || effort === "high")
2122
+ return effort;
2123
+ if (effort === "max")
2124
+ return "max";
2125
+ if (model.modelId === "claude-opus-4-7" && effort === "xhigh")
2126
+ return "xhigh";
2127
+ return undefined;
2128
+ }
2129
+ function anthropicSamplingPayload(model, temperature, topP, thinking) {
2130
+ if (model.modelId === "claude-opus-4-7")
2131
+ return {};
2132
+ if (anthropicThinkingConstrainsSampling(thinking)) {
2133
+ return { temperature: 1 };
2134
+ }
2135
+ return {
2136
+ temperature,
2137
+ top_p: topP
2138
+ };
2139
+ }
2140
+ function bearerHeaders(apiKey) {
2141
+ return {
2142
+ authorization: apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`
2143
+ };
2144
+ }
2145
+ function anthropicHeaders(apiKey) {
2146
+ return {
2147
+ "x-api-key": apiKey,
2148
+ "anthropic-version": "2023-06-01"
2149
+ };
2150
+ }
2151
+ function stripUndefined(value) {
2152
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
2153
+ }
2154
+ export function throwIfAborted(signal) {
2155
+ if (signal?.aborted) {
2156
+ throw signal.reason instanceof Error ? signal.reason : new Error("Request aborted.");
2157
+ }
2158
+ }
2159
+ function googleAiStudioHeaders(apiKey) {
2160
+ return { "x-goog-api-key": apiKey };
2161
+ }
2162
+ function googleAiStudioUrl(provider, model) {
2163
+ return `${provider.baseUrl}${model.endpoint}/${model.modelId}:generateContent`;
2164
+ }
2165
+ const googleSafetySettings = [
2166
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" },
2167
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" },
2168
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" },
2169
+ { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" }
2170
+ ];
2171
+ function googleUserTurn(text) {
2172
+ return { role: "user", parts: [{ text }] };
2173
+ }
2174
+ function googleGenerationConfig(model, opts) {
2175
+ return stripUndefined({
2176
+ temperature: opts.temperature,
2177
+ topP: opts.topP,
2178
+ maxOutputTokens: opts.maxOutputTokens,
2179
+ stopSequences: opts.stopSequences?.length ? opts.stopSequences.slice(0, 5) : undefined,
2180
+ thinkingConfig: googleThinkingConfig(model, opts.reasoning)
2181
+ });
2182
+ }
2183
+ function googleThinkingConfig(model, reasoning) {
2184
+ const modelId = model.modelId;
2185
+ // Gemini 3.x flash family uses thinkingLevel (minimal | low | medium | high).
2186
+ if (modelId.startsWith("gemini-3")) {
2187
+ if (reasoning?.enabled === false)
2188
+ return { thinkingLevel: "minimal" };
2189
+ if (reasoning?.effort)
2190
+ return { thinkingLevel: reasoning.effort };
2191
+ return undefined;
2192
+ }
2193
+ // Gemini 2.5 Flash: thinkingBudget; 0 disables; max 24576.
2194
+ if (modelId === "gemini-2.5-flash") {
2195
+ if (reasoning?.enabled === false)
2196
+ return { thinkingBudget: 0 };
2197
+ if (reasoning?.budgetTokens && reasoning.budgetTokens > 0) {
2198
+ return { thinkingBudget: Math.min(reasoning.budgetTokens, 24576) };
2199
+ }
2200
+ return undefined;
2201
+ }
2202
+ // Gemini 2.5 Flash Lite: 0 disables, -1 requests dynamic thinking, positive budgets clamp to [512, 24576].
2203
+ if (modelId === "gemini-2.5-flash-lite") {
2204
+ if (reasoning?.enabled === false)
2205
+ return { thinkingBudget: 0 };
2206
+ if (reasoning?.enabled === true && !reasoning.budgetTokens)
2207
+ return { thinkingBudget: -1 };
2208
+ if (reasoning?.budgetTokens && reasoning.budgetTokens > 0) {
2209
+ return { thinkingBudget: Math.max(512, Math.min(reasoning.budgetTokens, 24576)) };
2210
+ }
2211
+ return undefined;
2212
+ }
2213
+ return undefined;
2214
+ }
2215
+ function googleAiStudioTool(name, description, parameters) {
2216
+ return { functionDeclarations: [{ name, description, parameters: googleAiStudioSchema(parameters) }] };
2217
+ }
2218
+ function googleAiStudioSchema(schema) {
2219
+ if (Array.isArray(schema)) {
2220
+ return schema.map((item) => googleAiStudioSchema(item));
2221
+ }
2222
+ if (!isRecord(schema))
2223
+ return schema;
2224
+ const converted = {};
2225
+ let nullable = false;
2226
+ for (const [key, value] of Object.entries(schema)) {
2227
+ if (key === "additionalProperties" || key === "anyOf")
2228
+ continue;
2229
+ if (key === "enum" && Array.isArray(value)) {
2230
+ converted.enum = value.map(String);
2231
+ continue;
2232
+ }
2233
+ if (key === "type" && Array.isArray(value)) {
2234
+ const nonNullTypes = value.filter((item) => item !== "null");
2235
+ nullable = nonNullTypes.length !== value.length;
2236
+ converted.type = nonNullTypes.length === 1 ? nonNullTypes[0] : googleAiStudioSchema(nonNullTypes);
2237
+ continue;
2238
+ }
2239
+ converted[key] = googleAiStudioSchema(value);
2240
+ }
2241
+ const anyOf = schema.anyOf;
2242
+ if (Array.isArray(anyOf)) {
2243
+ const nonNullSchemas = anyOf.filter((item) => !(isRecord(item) && item.type === "null"));
2244
+ const hasNullSchema = nonNullSchemas.length !== anyOf.length;
2245
+ if (hasNullSchema && nonNullSchemas.length === 1) {
2246
+ const base = googleAiStudioSchema(nonNullSchemas[0]);
2247
+ if (isRecord(base)) {
2248
+ const parentFields = { ...converted };
2249
+ Object.assign(converted, base, parentFields);
2250
+ nullable = true;
2251
+ }
2252
+ }
2253
+ else {
2254
+ converted.anyOf = anyOf.map((item) => googleAiStudioSchema(item));
2255
+ }
2256
+ }
2257
+ if (nullable) {
2258
+ converted.nullable = true;
2259
+ }
2260
+ return converted;
2261
+ }
2262
+ function googleForceToolConfig(name) {
2263
+ return { functionCallingConfig: { mode: "ANY", allowedFunctionNames: [name] } };
2264
+ }
2265
+ function googleAiStudioOrchestratorTool(availableWaifuIds, replyRequired = false) {
2266
+ return googleAiStudioTool(ORCHESTRATOR_TOOL_NAME, ORCHESTRATOR_TOOL_DESCRIPTION, orchestratorToolParameters(availableWaifuIds, replyRequired));
2267
+ }
2268
+ function googleAiStudioStageManagerTool(availableWaifuIds) {
2269
+ return googleAiStudioTool(STAGE_MANAGER_TOOL_NAME, STAGE_MANAGER_TOOL_DESCRIPTION, flatStageManagerToolParameters(availableWaifuIds));
2270
+ }
2271
+ function googleAiStudioObserverTool(availableWaifuIds) {
2272
+ return googleAiStudioTool(OBSERVER_TOOL_NAME, OBSERVER_TOOL_DESCRIPTION, observerToolParameters(availableWaifuIds));
2273
+ }
2274
+ function googleAiStudioReviewerTool() {
2275
+ return googleAiStudioTool(REVIEWER_TOOL_NAME, REVIEWER_TOOL_DESCRIPTION, REVIEWER_TOOL_PARAMETERS);
2276
+ }
2277
+ function googleAiStudioPickNextWaifuTool(availableWaifuIds) {
2278
+ return googleAiStudioTool(PICK_NEXT_WAIFU_TOOL_NAME, PICK_NEXT_WAIFU_TOOL_DESCRIPTION, pickNextWaifuToolParameters(availableWaifuIds));
2279
+ }
2280
+ function googleAiStudioShortTermMemoryTool() {
2281
+ return googleAiStudioTool(SHORT_TERM_MEMORY_TOOL_NAME, SHORT_TERM_MEMORY_TOOL_DESCRIPTION, shortTermMemoryToolParameters());
2282
+ }
2283
+ function googleAiStudioPickNextWaifuToolPayload(model, request) {
2284
+ if (!shouldExposePickNextWaifuTool(model, request))
2285
+ return {};
2286
+ return {
2287
+ tools: [googleAiStudioPickNextWaifuTool(request.availableWaifuIds)],
2288
+ toolConfig: { functionCallingConfig: { mode: "AUTO" } }
2289
+ };
2290
+ }
2291
+ function googleAiStudioShortTermMemoryToolPayload(model, request) {
2292
+ if (!shouldExposeShortTermMemoryTool(model, request))
2293
+ return {};
2294
+ return {
2295
+ tools: [googleAiStudioShortTermMemoryTool()],
2296
+ toolConfig: { functionCallingConfig: { mode: "AUTO" } }
2297
+ };
2298
+ }
2299
+ function googleAiStudioWaifuToolsPayload(model, request) {
2300
+ const pick = googleAiStudioPickNextWaifuToolPayload(model, request);
2301
+ const mem = googleAiStudioShortTermMemoryToolPayload(model, request);
2302
+ const tools = [...(pick.tools ?? []), ...(mem.tools ?? [])];
2303
+ if (!tools.length)
2304
+ return {};
2305
+ const toolConfig = pick.toolConfig ?? mem.toolConfig;
2306
+ return toolConfig ? { tools, toolConfig } : { tools };
2307
+ }
2308
+ async function contextToGoogleMessagesForWaifu(messages, includeImages) {
2309
+ return Promise.all(messages.map(async (message) => {
2310
+ const role = roleForWaifuContext(message) === "assistant" ? "model" : "user";
2311
+ const text = formatWaifuContextBlock(message);
2312
+ const imageParts = includeImages && role === "user" ? await googleImageParts(message) : [];
2313
+ return {
2314
+ role,
2315
+ parts: [{ text }, ...imageParts]
2316
+ };
2317
+ }));
2318
+ }
2319
+ async function googleImageParts(message) {
2320
+ const parts = await Promise.all((message.images ?? []).map((image) => googleInlineImagePart(image)));
2321
+ return parts.filter((part) => part !== undefined);
2322
+ }
2323
+ async function googleInlineImagePart(image) {
2324
+ try {
2325
+ const response = await fetch(image.url);
2326
+ if (!response.ok)
2327
+ return undefined;
2328
+ const headerType = response.headers.get("content-type") ?? undefined;
2329
+ const fromHeader = headerType && headerType.toLowerCase().startsWith("image/")
2330
+ ? headerType.split(";")[0].trim()
2331
+ : undefined;
2332
+ const mimeType = fromHeader ?? image.contentType ?? guessImageMimeFromUrl(image.url);
2333
+ if (!mimeType || !mimeType.startsWith("image/"))
2334
+ return undefined;
2335
+ const buffer = await response.arrayBuffer();
2336
+ const data = Buffer.from(buffer).toString("base64");
2337
+ return { inlineData: { mimeType, data } };
2338
+ }
2339
+ catch {
2340
+ return undefined;
2341
+ }
2342
+ }
2343
+ function guessImageMimeFromUrl(url) {
2344
+ const match = url.toLowerCase().match(/\.([a-z0-9]+)(?:\?|#|$)/);
2345
+ if (!match)
2346
+ return undefined;
2347
+ switch (match[1]) {
2348
+ case "png": return "image/png";
2349
+ case "jpg":
2350
+ case "jpeg": return "image/jpeg";
2351
+ case "webp": return "image/webp";
2352
+ case "gif": return "image/gif";
2353
+ case "heic": return "image/heic";
2354
+ case "heif": return "image/heif";
2355
+ default: return undefined;
2356
+ }
2357
+ }
2358
+ function extractGoogleText(json) {
2359
+ const parsed = json;
2360
+ return parsed.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2361
+ }
2362
+ function extractGoogleWaifuResult(json, availableWaifuIds) {
2363
+ const parsed = json;
2364
+ const parts = parsed.candidates?.[0]?.content?.parts ?? [];
2365
+ const pickCall = parts.find((part) => part.functionCall?.name === PICK_NEXT_WAIFU_TOOL_NAME)?.functionCall;
2366
+ const shortTermMemoryEntries = parts
2367
+ .filter((part) => part.functionCall?.name === SHORT_TERM_MEMORY_TOOL_NAME)
2368
+ .map((part) => parseShortTermMemoryArguments(part.functionCall?.args))
2369
+ .filter((entry) => Boolean(entry));
2370
+ return {
2371
+ content: extractGoogleText(parsed),
2372
+ ...parsePickedNextWaifu(pickCall?.args, availableWaifuIds),
2373
+ ...(shortTermMemoryEntries.length ? { shortTermMemoryEntries } : {})
2374
+ };
2375
+ }
2376
+ function extractGoogleToolArguments(json, toolName) {
2377
+ const parsed = json;
2378
+ const parts = parsed.candidates?.[0]?.content?.parts ?? [];
2379
+ const call = parts.find((part) => part.functionCall?.name === toolName)?.functionCall
2380
+ ?? parts.find((part) => part.functionCall)?.functionCall;
2381
+ const args = call?.args;
2382
+ if (typeof args === "string")
2383
+ return args;
2384
+ if (args && typeof args === "object")
2385
+ return JSON.stringify(args);
2386
+ return extractGoogleText(parsed);
2387
+ }
2388
+ //# sourceMappingURL=pipelines.js.map