@warlock.js/core 4.5.0 → 4.6.0

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 (200) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/esm/ai/src/agent/agent.mjs +126 -7
  3. package/esm/ai/src/agent/agent.mjs.map +1 -1
  4. package/esm/ai/src/agent/signature.mjs +57 -0
  5. package/esm/ai/src/agent/signature.mjs.map +1 -0
  6. package/esm/ai/src/agent/snapshot.mjs +101 -0
  7. package/esm/ai/src/agent/snapshot.mjs.map +1 -0
  8. package/esm/ai/src/ai.mjs +37 -1
  9. package/esm/ai/src/ai.mjs.map +1 -1
  10. package/esm/ai/src/errors/agent-drift-error.mjs +31 -0
  11. package/esm/ai/src/errors/agent-drift-error.mjs.map +1 -0
  12. package/esm/ai/src/errors/index.mjs +2 -0
  13. package/esm/ai/src/errors/planner-drift-error.mjs +33 -0
  14. package/esm/ai/src/errors/planner-drift-error.mjs.map +1 -0
  15. package/esm/ai/src/image/image-cost.mjs +55 -0
  16. package/esm/ai/src/image/image-cost.mjs.map +1 -0
  17. package/esm/ai/src/image/image.mjs +112 -0
  18. package/esm/ai/src/image/image.mjs.map +1 -0
  19. package/esm/ai/src/image/index.mjs +4 -0
  20. package/esm/ai/src/index.mjs +20 -0
  21. package/esm/ai/src/planner/planner-run.mjs +161 -6
  22. package/esm/ai/src/planner/planner-run.mjs.map +1 -1
  23. package/esm/ai/src/planner/planner.mjs +25 -1
  24. package/esm/ai/src/planner/planner.mjs.map +1 -1
  25. package/esm/ai/src/planner/snapshot.mjs +95 -0
  26. package/esm/ai/src/planner/snapshot.mjs.map +1 -0
  27. package/esm/ai/src/rag/hybrid/bm25.mjs +51 -0
  28. package/esm/ai/src/rag/hybrid/bm25.mjs.map +1 -0
  29. package/esm/ai/src/rag/hybrid/hybrid-rank.mjs +29 -0
  30. package/esm/ai/src/rag/hybrid/hybrid-rank.mjs.map +1 -0
  31. package/esm/ai/src/rag/hybrid/rrf.mjs +30 -0
  32. package/esm/ai/src/rag/hybrid/rrf.mjs.map +1 -0
  33. package/esm/ai/src/rag/index.mjs +11 -0
  34. package/esm/ai/src/rag/loaders/errors.mjs +25 -0
  35. package/esm/ai/src/rag/loaders/errors.mjs.map +1 -0
  36. package/esm/ai/src/rag/loaders/index.mjs +7 -0
  37. package/esm/ai/src/rag/loaders/load-html.mjs +138 -0
  38. package/esm/ai/src/rag/loaders/load-html.mjs.map +1 -0
  39. package/esm/ai/src/rag/loaders/load-pdf.mjs +150 -0
  40. package/esm/ai/src/rag/loaders/load-pdf.mjs.map +1 -0
  41. package/esm/ai/src/rag/loaders/load-text.mjs +60 -0
  42. package/esm/ai/src/rag/loaders/load-text.mjs.map +1 -0
  43. package/esm/ai/src/rag/loaders/load-web.mjs +89 -0
  44. package/esm/ai/src/rag/loaders/load-web.mjs.map +1 -0
  45. package/esm/ai/src/rag/store/pg-vector-store.mjs +328 -0
  46. package/esm/ai/src/rag/store/pg-vector-store.mjs.map +1 -0
  47. package/esm/ai/src/rag/transforms/multi-query.mjs +41 -0
  48. package/esm/ai/src/rag/transforms/multi-query.mjs.map +1 -0
  49. package/esm/ai/src/speech/index.mjs +3 -0
  50. package/esm/ai/src/speech/speech.mjs +122 -0
  51. package/esm/ai/src/speech/speech.mjs.map +1 -0
  52. package/esm/ai/src/supervisor/entries.mjs +2 -2
  53. package/esm/ai/src/supervisor/entries.mjs.map +1 -1
  54. package/esm/ai/src/transcribe/audio-input.mjs +84 -0
  55. package/esm/ai/src/transcribe/audio-input.mjs.map +1 -0
  56. package/esm/ai/src/transcribe/index.mjs +4 -0
  57. package/esm/ai/src/transcribe/transcribe.mjs +127 -0
  58. package/esm/ai/src/transcribe/transcribe.mjs.map +1 -0
  59. package/esm/ai-openai/src/image.mjs +5 -0
  60. package/esm/ai-openai/src/index.mjs +3 -0
  61. package/esm/ai-openai/src/sdk.mjs +3 -0
  62. package/esm/ai-openai/src/speech.mjs +5 -0
  63. package/esm/ai-openai/src/transcription.mjs +6 -0
  64. package/esm/ai-openai/src/utils/index.mjs +1 -0
  65. package/esm/ai-openai/src/utils/to-openai-messages.mjs +3 -0
  66. package/esm/cli/commands/doctor/checks/config.check.mjs +42 -0
  67. package/esm/cli/commands/doctor/checks/config.check.mjs.map +1 -0
  68. package/esm/cli/commands/doctor/checks/connectors.check.mjs +28 -0
  69. package/esm/cli/commands/doctor/checks/connectors.check.mjs.map +1 -0
  70. package/esm/cli/commands/doctor/checks/health.check.mjs +35 -0
  71. package/esm/cli/commands/doctor/checks/health.check.mjs.map +1 -0
  72. package/esm/cli/commands/doctor/checks/index.mjs +25 -0
  73. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -0
  74. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +90 -0
  75. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -0
  76. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs +69 -0
  77. package/esm/cli/commands/doctor/checks/release-hygiene.check.mjs.map +1 -0
  78. package/esm/cli/commands/doctor/checks/routes.check.mjs +30 -0
  79. package/esm/cli/commands/doctor/checks/routes.check.mjs.map +1 -0
  80. package/esm/cli/commands/doctor/doctor-command.action.mjs +28 -0
  81. package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -0
  82. package/esm/cli/commands/doctor/format-report.mjs +44 -0
  83. package/esm/cli/commands/doctor/format-report.mjs.map +1 -0
  84. package/esm/cli/commands/doctor/run-checks.mjs +50 -0
  85. package/esm/cli/commands/doctor/run-checks.mjs.map +1 -0
  86. package/esm/cli/commands/doctor.command.mjs +29 -0
  87. package/esm/cli/commands/doctor.command.mjs.map +1 -0
  88. package/esm/cli/commands/generate/templates/stubs.mjs +6 -8
  89. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  90. package/esm/cli/commands/routes/format-routes-table.mjs +129 -0
  91. package/esm/cli/commands/routes/format-routes-table.mjs.map +1 -0
  92. package/esm/cli/commands/routes/route-row.mjs +92 -0
  93. package/esm/cli/commands/routes/route-row.mjs.map +1 -0
  94. package/esm/cli/commands/routes/routes-command.action.mjs +43 -0
  95. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -0
  96. package/esm/cli/commands/routes.command.mjs +51 -0
  97. package/esm/cli/commands/routes.command.mjs.map +1 -0
  98. package/esm/cli/commands/seed.command.mjs +5 -0
  99. package/esm/cli/commands/seed.command.mjs.map +1 -1
  100. package/esm/cli/framework-cli-commands.mjs +4 -0
  101. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  102. package/esm/config/config-getter.mjs +5 -5
  103. package/esm/config/config-getter.mjs.map +1 -1
  104. package/esm/config/config-loader.mjs +2 -2
  105. package/esm/config/config-loader.mjs.map +1 -1
  106. package/esm/connectors/access-connector.mjs +2 -2
  107. package/esm/connectors/access-connector.mjs.map +1 -1
  108. package/esm/connectors/ai-connector.mjs +2 -2
  109. package/esm/connectors/ai-connector.mjs.map +1 -1
  110. package/esm/connectors/cache-connector.mjs +2 -2
  111. package/esm/connectors/cache-connector.mjs.map +1 -1
  112. package/esm/connectors/database-connector.mjs +2 -2
  113. package/esm/connectors/database-connector.mjs.map +1 -1
  114. package/esm/connectors/herald-connector.mjs +2 -2
  115. package/esm/connectors/herald-connector.mjs.map +1 -1
  116. package/esm/connectors/http-connector.d.mts.map +1 -1
  117. package/esm/connectors/http-connector.mjs +9 -7
  118. package/esm/connectors/http-connector.mjs.map +1 -1
  119. package/esm/connectors/logger-connector.mjs +2 -2
  120. package/esm/connectors/logger-connector.mjs.map +1 -1
  121. package/esm/connectors/mail-connector.mjs +2 -2
  122. package/esm/connectors/mail-connector.mjs.map +1 -1
  123. package/esm/connectors/notifications-connector.mjs +2 -2
  124. package/esm/connectors/notifications-connector.mjs.map +1 -1
  125. package/esm/connectors/socket-connector.mjs +3 -3
  126. package/esm/connectors/socket-connector.mjs.map +1 -1
  127. package/esm/database/index.d.mts +2 -1
  128. package/esm/database/index.mjs +1 -0
  129. package/esm/database/seed-command-action.mjs +44 -4
  130. package/esm/database/seed-command-action.mjs.map +1 -1
  131. package/esm/database/seeds/seed-records-table-migration.mjs +36 -0
  132. package/esm/database/seeds/seed-records-table-migration.mjs.map +1 -0
  133. package/esm/database/seeds/seeder.d.mts +22 -5
  134. package/esm/database/seeds/seeder.d.mts.map +1 -1
  135. package/esm/database/seeds/seeder.errors.d.mts +22 -0
  136. package/esm/database/seeds/seeder.errors.d.mts.map +1 -0
  137. package/esm/database/seeds/seeder.errors.mjs +29 -0
  138. package/esm/database/seeds/seeder.errors.mjs.map +1 -0
  139. package/esm/database/seeds/seeder.mjs.map +1 -1
  140. package/esm/database/seeds/seeders.manager.mjs +90 -11
  141. package/esm/database/seeds/seeders.manager.mjs.map +1 -1
  142. package/esm/database/seeds/types.d.mts +117 -1
  143. package/esm/database/seeds/types.d.mts.map +1 -1
  144. package/esm/database/seeds/utils.mjs +6 -1
  145. package/esm/database/seeds/utils.mjs.map +1 -1
  146. package/esm/dev-server/module-loader.d.mts +12 -0
  147. package/esm/dev-server/module-loader.d.mts.map +1 -1
  148. package/esm/dev-server/module-loader.mjs +30 -2
  149. package/esm/dev-server/module-loader.mjs.map +1 -1
  150. package/esm/http/config.mjs +2 -2
  151. package/esm/http/config.mjs.map +1 -1
  152. package/esm/http/createHttpApplication.mjs +2 -2
  153. package/esm/http/createHttpApplication.mjs.map +1 -1
  154. package/esm/http/health.d.mts +14 -0
  155. package/esm/http/health.d.mts.map +1 -1
  156. package/esm/http/health.mjs +16 -0
  157. package/esm/http/health.mjs.map +1 -1
  158. package/esm/http/middleware/idempotency.middleware.mjs +5 -5
  159. package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
  160. package/esm/http/middleware/inject-request-context.mjs +2 -2
  161. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  162. package/esm/http/middleware/maintenance.middleware.mjs +4 -4
  163. package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
  164. package/esm/http/plugins.mjs +9 -9
  165. package/esm/http/plugins.mjs.map +1 -1
  166. package/esm/http/response.mjs +5 -5
  167. package/esm/http/response.mjs.map +1 -1
  168. package/esm/http/server.mjs +4 -4
  169. package/esm/http/server.mjs.map +1 -1
  170. package/esm/index.d.mts +4 -3
  171. package/esm/index.mjs +2 -1
  172. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +28 -1
  173. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts.map +1 -1
  174. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs +39 -0
  175. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  176. package/esm/repositories/contracts/index.d.mts +1 -1
  177. package/esm/repositories/contracts/query-builder.contract.d.mts +87 -1
  178. package/esm/repositories/contracts/query-builder.contract.d.mts.map +1 -1
  179. package/esm/repositories/index.d.mts +1 -1
  180. package/esm/repositories/repository.manager.d.mts +99 -1
  181. package/esm/repositories/repository.manager.d.mts.map +1 -1
  182. package/esm/repositories/repository.manager.mjs +128 -0
  183. package/esm/repositories/repository.manager.mjs.map +1 -1
  184. package/esm/router/router.d.mts +9 -0
  185. package/esm/router/router.d.mts.map +1 -1
  186. package/esm/router/router.mjs +21 -1
  187. package/esm/router/router.mjs.map +1 -1
  188. package/esm/utils/paths.mjs +2 -2
  189. package/esm/utils/paths.mjs.map +1 -1
  190. package/esm/validation/validateAll.mjs +2 -2
  191. package/esm/validation/validateAll.mjs.map +1 -1
  192. package/llms-full.txt +427 -45
  193. package/llms.txt +4 -2
  194. package/package.json +9 -9
  195. package/skills/README.md +65 -0
  196. package/skills/health-checks/SKILL.md +13 -0
  197. package/skills/use-repository/SKILL.md +38 -1
  198. package/skills/warlock-doctor/SKILL.md +135 -0
  199. package/skills/warlock-routes/SKILL.md +91 -0
  200. package/skills/write-seeder/SKILL.md +143 -45
@@ -0,0 +1,122 @@
1
+ import { AIError } from "../errors/ai-error.mjs";
2
+ import { ProviderError } from "../errors/provider-error.mjs";
3
+ import { computeCost } from "../utils/compute-cost.mjs";
4
+ import { generateRunId } from "../utils/generate-run-id.mjs";
5
+ import { stampReportLineage } from "../utils/stamp-report-lineage.mjs";
6
+ import { notifyObservers } from "../observe/resolve-observers.mjs";
7
+
8
+ //#region ../@warlock.js/ai/src/speech/speech.ts
9
+ /**
10
+ * Synthesize speech from text — the text-to-speech verb of the
11
+ * output-modality track (Theme I), sibling to `ai.image()`. Wraps a
12
+ * {@link SpeechModelContract} (from `openai.speech(...)`) in the
13
+ * framework's uniform result contract:
14
+ *
15
+ * - **Never throws.** Provider failures surface as a typed `AIError` on
16
+ * `result.error`; `result.data` is then `undefined`.
17
+ * - **Cost-truth.** `result.usage.cost` is filled per-character
18
+ * (`tts-1`) or per-token (`gpt-4o-mini-tts`), folding into the same
19
+ * `Usage.cost` rollup as text.
20
+ * - **Observable.** The completed {@link SpeechReport} routes to any
21
+ * registered `Observer` (panoptic, OTel, …) via the `observe` seam.
22
+ *
23
+ * @example
24
+ * const openai = new OpenAISDK({ apiKey });
25
+ * const { data, error } = await ai.speech({
26
+ * model: openai.speech({ name: "tts-1", voice: "alloy" }),
27
+ * text: "Your order has shipped.",
28
+ * format: "mp3",
29
+ * });
30
+ * if (!error) await fs.writeFile("ship.mp3", Buffer.from(data.audio.base64, "base64"));
31
+ */
32
+ async function speech(params) {
33
+ const { model, text } = params;
34
+ const runId = generateRunId("speech");
35
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
36
+ const startPerf = performance.now();
37
+ const usage = {
38
+ input: 0,
39
+ output: 0,
40
+ total: 0
41
+ };
42
+ let data;
43
+ let error;
44
+ let status = "completed";
45
+ let characters = 0;
46
+ try {
47
+ const response = await model.generate(text, {
48
+ voice: params.voice,
49
+ format: params.format,
50
+ speed: params.speed,
51
+ instructions: params.instructions,
52
+ signal: params.signal,
53
+ ...params.options
54
+ });
55
+ Object.assign(usage, response.usage);
56
+ characters = response.characters;
57
+ if (usage.cost === void 0) {
58
+ const cost = computeSpeechCost(usage, characters, model.pricing);
59
+ if (cost !== void 0) usage.cost = cost;
60
+ }
61
+ data = { audio: response.audio };
62
+ } catch (thrown) {
63
+ error = thrown instanceof AIError ? thrown : new ProviderError(toMessage(thrown), { cause: thrown });
64
+ status = params.signal?.aborted ? "cancelled" : "failed";
65
+ }
66
+ const report = {
67
+ runId,
68
+ rootRunId: runId,
69
+ name: params.name ?? "speech",
70
+ type: "speech",
71
+ status,
72
+ error,
73
+ startedAt,
74
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
75
+ duration: performance.now() - startPerf,
76
+ usage,
77
+ children: [],
78
+ model: {
79
+ name: model.name,
80
+ provider: model.provider
81
+ },
82
+ characters,
83
+ reportSchemaVersion: 1
84
+ };
85
+ stampReportLineage(report, {
86
+ rootRunId: runId,
87
+ sessionId: params.sessionId
88
+ });
89
+ await notifyObservers(params.observe, report);
90
+ return {
91
+ type: "speech",
92
+ data,
93
+ error,
94
+ usage,
95
+ report
96
+ };
97
+ }
98
+ /**
99
+ * Price a TTS run: `perMillionCharacters × characters` (per-character
100
+ * metering, attributed to `cost.input`) wins when configured, otherwise
101
+ * the standard token math. Returns `undefined` when no usable pricing
102
+ * is present.
103
+ */
104
+ function computeSpeechCost(usage, characters, pricing) {
105
+ if (!pricing) return;
106
+ if (pricing.perMillionCharacters !== void 0) return {
107
+ input: characters * pricing.perMillionCharacters / 1e6,
108
+ output: 0
109
+ };
110
+ if (pricing.input !== void 0 && pricing.output !== void 0) return computeCost(usage, {
111
+ input: pricing.input,
112
+ output: pricing.output
113
+ });
114
+ }
115
+ /** Best-effort message for a non-`AIError` thrown value. */
116
+ function toMessage(thrown) {
117
+ return thrown instanceof Error ? thrown.message : String(thrown);
118
+ }
119
+
120
+ //#endregion
121
+ export { speech };
122
+ //# sourceMappingURL=speech.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.mjs","names":[],"sources":["../../../../../../../../../@warlock.js/ai/src/speech/speech.ts"],"sourcesContent":["import type {\n GeneratedAudio,\n SpeechModelContract,\n SpeechModelPricing,\n} from \"../contracts/speech-model.contract\";\nimport type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { REPORT_SCHEMA_VERSION } from \"../contracts/result/base-report.type\";\nimport type { ExecuteResult } from \"../contracts/result/execute-result.type\";\nimport type { ModelPricing } from \"../contracts/result/model-pricing.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport { AIError } from \"../errors/ai-error\";\nimport { ProviderError } from \"../errors/provider-error\";\nimport type { FlowObserveOption } from \"../observe/resolve-observers\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport { computeCost } from \"../utils/compute-cost\";\nimport { generateRunId } from \"../utils/generate-run-id\";\nimport { stampReportLineage } from \"../utils/stamp-report-lineage\";\n\n/** Parameters for {@link speech}. `model` comes from `sdk.speech({ name })`. */\nexport type SpeechParams = {\n /** The TTS model to synthesize with. */\n model: SpeechModelContract;\n /** The text to speak. */\n text: string;\n /** Voice id/name; overrides the model's default voice. */\n voice?: string;\n /** Output container (`\"mp3\"` / `\"opus\"` / `\"aac\"` / `\"flac\"` / `\"wav\"` / `\"pcm\"`). */\n format?: string;\n /** Playback speed multiplier. */\n speed?: number;\n /** Extra tone/delivery steering (model-dependent). */\n instructions?: string;\n /** Cancellation handle. */\n signal?: AbortSignal;\n /** Observability routing — same `observe` seam as agents. */\n observe?: FlowObserveOption;\n /** Groups this call into a session for flat cost/trace queries. */\n sessionId?: string;\n /** Report node name (defaults to `\"speech\"`). */\n name?: string;\n /** Provider-specific options forwarded verbatim to the adapter. */\n options?: Record<string, unknown>;\n};\n\n/** Success payload of a {@link speech} run. */\nexport type SpeechData = {\n /** The synthesized audio, normalized to the discriminated shape. */\n audio: GeneratedAudio;\n};\n\n/** The report node a {@link speech} run produces (`type: \"speech\"`). */\nexport type SpeechReport = BaseReport & {\n type: \"speech\";\n /** Identity of the TTS model this run used. */\n model: { name: string; provider: string };\n /** Number of input characters synthesized (0 on failure). */\n characters: number;\n};\n\n/** Result envelope of {@link speech} — the uniform `{ data, error, usage, report }`. */\nexport type SpeechResult = ExecuteResult<SpeechData> & {\n type: \"speech\";\n report: SpeechReport;\n};\n\n/**\n * Synthesize speech from text — the text-to-speech verb of the\n * output-modality track (Theme I), sibling to `ai.image()`. Wraps a\n * {@link SpeechModelContract} (from `openai.speech(...)`) in the\n * framework's uniform result contract:\n *\n * - **Never throws.** Provider failures surface as a typed `AIError` on\n * `result.error`; `result.data` is then `undefined`.\n * - **Cost-truth.** `result.usage.cost` is filled per-character\n * (`tts-1`) or per-token (`gpt-4o-mini-tts`), folding into the same\n * `Usage.cost` rollup as text.\n * - **Observable.** The completed {@link SpeechReport} routes to any\n * registered `Observer` (panoptic, OTel, …) via the `observe` seam.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey });\n * const { data, error } = await ai.speech({\n * model: openai.speech({ name: \"tts-1\", voice: \"alloy\" }),\n * text: \"Your order has shipped.\",\n * format: \"mp3\",\n * });\n * if (!error) await fs.writeFile(\"ship.mp3\", Buffer.from(data.audio.base64, \"base64\"));\n */\nexport async function speech(params: SpeechParams): Promise<SpeechResult> {\n const { model, text } = params;\n\n const runId = generateRunId(\"speech\");\n const startedAt = new Date().toISOString();\n const startPerf = performance.now();\n\n const usage: Usage = { input: 0, output: 0, total: 0 };\n let data: SpeechData | undefined;\n let error: AIError | undefined;\n let status: SpeechReport[\"status\"] = \"completed\";\n let characters = 0;\n\n try {\n const response = await model.generate(text, {\n voice: params.voice,\n format: params.format,\n speed: params.speed,\n instructions: params.instructions,\n signal: params.signal,\n ...params.options,\n });\n\n Object.assign(usage, response.usage);\n characters = response.characters;\n\n if (usage.cost === undefined) {\n const cost = computeSpeechCost(usage, characters, model.pricing);\n if (cost !== undefined) {\n usage.cost = cost;\n }\n }\n\n data = { audio: response.audio };\n } catch (thrown) {\n error =\n thrown instanceof AIError ? thrown : new ProviderError(toMessage(thrown), { cause: thrown });\n status = params.signal?.aborted ? \"cancelled\" : \"failed\";\n }\n\n const report: SpeechReport = {\n runId,\n rootRunId: runId,\n name: params.name ?? \"speech\",\n type: \"speech\",\n status,\n error,\n startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - startPerf,\n usage,\n children: [],\n model: { name: model.name, provider: model.provider },\n characters,\n reportSchemaVersion: REPORT_SCHEMA_VERSION,\n };\n\n stampReportLineage(report, { rootRunId: runId, sessionId: params.sessionId });\n\n await notifyObservers(params.observe, report);\n\n return { type: \"speech\", data, error, usage, report };\n}\n\n/**\n * Price a TTS run: `perMillionCharacters × characters` (per-character\n * metering, attributed to `cost.input`) wins when configured, otherwise\n * the standard token math. Returns `undefined` when no usable pricing\n * is present.\n */\nfunction computeSpeechCost(\n usage: Usage,\n characters: number,\n pricing: SpeechModelPricing | undefined,\n): ModelPricing | undefined {\n if (!pricing) {\n return undefined;\n }\n\n if (pricing.perMillionCharacters !== undefined) {\n return { input: (characters * pricing.perMillionCharacters) / 1_000_000, output: 0 };\n }\n\n if (pricing.input !== undefined && pricing.output !== undefined) {\n return computeCost(usage, { input: pricing.input, output: pricing.output });\n }\n\n return undefined;\n}\n\n/** Best-effort message for a non-`AIError` thrown value. */\nfunction toMessage(thrown: unknown): string {\n return thrown instanceof Error ? thrown.message : String(thrown);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,eAAsB,OAAO,QAA6C;CACxE,MAAM,EAAE,OAAO,SAAS;CAExB,MAAM,QAAQ,cAAc,QAAQ;CACpC,MAAM,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;CACzC,MAAM,YAAY,YAAY,IAAI;CAElC,MAAM,QAAe;EAAE,OAAO;EAAG,QAAQ;EAAG,OAAO;CAAE;CACrD,IAAI;CACJ,IAAI;CACJ,IAAI,SAAiC;CACrC,IAAI,aAAa;CAEjB,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,SAAS,MAAM;GAC1C,OAAO,OAAO;GACd,QAAQ,OAAO;GACf,OAAO,OAAO;GACd,cAAc,OAAO;GACrB,QAAQ,OAAO;GACf,GAAG,OAAO;EACZ,CAAC;EAED,OAAO,OAAO,OAAO,SAAS,KAAK;EACnC,aAAa,SAAS;EAEtB,IAAI,MAAM,SAAS,QAAW;GAC5B,MAAM,OAAO,kBAAkB,OAAO,YAAY,MAAM,OAAO;GAC/D,IAAI,SAAS,QACX,MAAM,OAAO;EAEjB;EAEA,OAAO,EAAE,OAAO,SAAS,MAAM;CACjC,SAAS,QAAQ;EACf,QACE,kBAAkB,UAAU,SAAS,IAAI,cAAc,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;EAC7F,SAAS,OAAO,QAAQ,UAAU,cAAc;CAClD;CAEA,MAAM,SAAuB;EAC3B;EACA,WAAW;EACX,MAAM,OAAO,QAAQ;EACrB,MAAM;EACN;EACA;EACA;EACA,0BAAS,IAAI,KAAK,EAAC,CAAC,YAAY;EAChC,UAAU,YAAY,IAAI,IAAI;EAC9B;EACA,UAAU,CAAC;EACX,OAAO;GAAE,MAAM,MAAM;GAAM,UAAU,MAAM;EAAS;EACpD;EACA;CACF;CAEA,mBAAmB,QAAQ;EAAE,WAAW;EAAO,WAAW,OAAO;CAAU,CAAC;CAE5E,MAAM,gBAAgB,OAAO,SAAS,MAAM;CAE5C,OAAO;EAAE,MAAM;EAAU;EAAM;EAAO;EAAO;CAAO;AACtD;;;;;;;AAQA,SAAS,kBACP,OACA,YACA,SAC0B;CAC1B,IAAI,CAAC,SACH;CAGF,IAAI,QAAQ,yBAAyB,QACnC,OAAO;EAAE,OAAQ,aAAa,QAAQ,uBAAwB;EAAW,QAAQ;CAAE;CAGrF,IAAI,QAAQ,UAAU,UAAa,QAAQ,WAAW,QACpD,OAAO,YAAY,OAAO;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;AAI9E;;AAGA,SAAS,UAAU,QAAyB;CAC1C,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACjE"}
@@ -178,8 +178,8 @@ function isDispatchableUnit(value) {
178
178
  return typeof candidate.name === "string" && typeof candidate.execute === "function";
179
179
  }
180
180
  function detectType(unit) {
181
- if (typeof unit.signature === "string") return "workflow";
182
- return "agent";
181
+ if (typeof unit.stream === "function") return "agent";
182
+ return "workflow";
183
183
  }
184
184
  function resolveAgentLikeDescription(intent, entryForm, unit, supervisorName) {
185
185
  const entryOverride = entryForm?.description;
@@ -1 +1 @@
1
- {"version":3,"file":"entries.mjs","names":[],"sources":["../../../../../../../../../@warlock.js/ai/src/supervisor/entries.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { Message } from \"../contracts/conversation-message.type\";\nimport type { EndSentinel } from \"../contracts/end.type\";\nimport type { AgentResult } from \"../contracts/result/agent-result.type\";\nimport type { WorkflowResult } from \"../contracts/result/workflow-result.type\";\nimport type { DispatchContext } from \"../contracts/supervisor/dispatch-context.type\";\nimport type {\n DispatchRawResult,\n IntentCallback,\n IntentEntry,\n IntentRunEntry,\n SupervisorIntentValue,\n} from \"../contracts/supervisor/intent-entry.type\";\nimport type { RouteContext } from \"../contracts/supervisor/route-context.type\";\nimport type { SupervisorConfig } from \"../contracts/supervisor/supervisor-config.type\";\nimport type { WorkflowInstance } from \"../contracts/workflow/workflow.contract\";\nimport { SupervisorFailedError } from \"../errors\";\n\n/**\n * Normalized internal representation of one entry in a supervisor's\n * `intents` map — resolved at factory time from one of the accepted\n * value forms (bare agent / workflow / callback / object entry).\n *\n * Carrying the explicit `type` discriminator keeps downstream code\n * (execution, signature, router-prompt) from having to re-detect\n * shape on every dispatch. The discriminated union below replaces\n * the flat-shape used in Phase 3 so callbacks can carry their own\n * function reference + dispatch-context-shaped resolvers.\n *\n * Discriminator renamed `kind` → `type` (Q12) for codebase-wide\n * consistency — every other discriminated result/report shape uses\n * `type`.\n */\nexport type ResolvedIntentEntry =\n | ResolvedAgentEntry\n | ResolvedWorkflowEntry\n | ResolvedCallbackEntry;\n\n/**\n * Successor directive function type — the resolver-time projection of\n * `IntentEntry.next` / `IntentRunEntry.next`. Single source of truth\n * across the three resolved variants.\n */\nexport type IntentNext = (ctx: DispatchContext) => string | string[] | EndSentinel | undefined;\n\n/**\n * Resolver-time projection of `IntentEntry.history` /\n * `RouterEntry.history` / `AckEntry.history`. Custom slicer that\n * REPLACES the default `historyWindow.<role>` slice.\n */\nexport type EntryHistorySlicer = (ctx: RouteContext) => Message[] | ReadonlyArray<Message>;\n\nexport type ResolvedAgentEntry = {\n intent: string;\n type: \"agent\";\n unit: AgentContract<unknown>;\n description: string;\n input?: (ctx: RouteContext) => string;\n /**\n * Per-dispatch placeholder values for the agent's systemPrompt\n * template. Forwarded as `agent.execute(input, { placeholders })`.\n * Phase 3.4 (Stage 4b) — replaces the dropped `composeAgentInput`\n * mechanism for threading state into agents.\n */\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n /**\n * Schema declaring this intent's slice of supervisor state. Agent\n * output is strip-merged against it; only validated keys appear on\n * `IterationSnapshot.result[intent].output` AND merge into\n * supervisor `state`.\n */\n output?: StandardSchemaV1<unknown>;\n /**\n * Successor directive (Stage 4d / Q24). When present, runs after\n * this branch's slice merges into state to choose the next dispatch\n * (or terminate) without invoking the router.\n */\n next?: IntentNext;\n /**\n * Custom history slicer — replaces the default\n * `historyWindow.agents` slice when supplied. See `IntentEntry.history`.\n */\n history?: EntryHistorySlicer;\n /**\n * Phase 5 / decisions §34. `\"stream\"` runs the agent without\n * structured-output coercion and writes the assembled prose into\n * `state[streamTo]`; `\"structured\"` is the default. Resolved at\n * factory time — `undefined` here is treated as `\"structured\"`.\n */\n mode?: \"structured\" | \"stream\";\n /** State key the assembled stream-mode prose writes into. Set iff `mode === \"stream\"`. */\n streamTo?: string;\n};\n\nexport type ResolvedWorkflowEntry = {\n intent: string;\n type: \"workflow\";\n unit: WorkflowInstance<unknown, unknown>;\n description: string;\n input?: (ctx: RouteContext) => string;\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n output?: StandardSchemaV1<unknown>;\n next?: IntentNext;\n history?: EntryHistorySlicer;\n};\n\nexport type ResolvedCallbackEntry = {\n intent: string;\n type: \"callback\";\n /**\n * The callback that actually runs at dispatch time. Always present\n * regardless of whether the user passed bare-function shorthand or\n * the `{ run, ... }` entry form.\n */\n callback: IntentCallback;\n /**\n * Description is required only when the supervisor uses a router.\n * Callback intents under a router are validated separately\n * (see {@link assertRouterDescriptions}); under deterministic\n * `route` mode this field is `undefined`.\n */\n description?: string;\n /**\n * Per-intent input resolver. Receives the upcoming\n * `DispatchContext` and returns the value forwarded as\n * `ctx.input` to the callback.\n */\n input?: (ctx: DispatchContext) => unknown;\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n /**\n * Schema declaring this callback's slice of state. Without it, the\n * full return value shallow-merges; with it, return is strip-merged\n * to declared keys before merging.\n */\n output?: StandardSchemaV1<unknown>;\n next?: IntentNext;\n};\n\n/**\n * Validate and normalize the `intents` map into resolved entries.\n * Runs at factory time — throws `SupervisorFailedError` on the first\n * malformed entry so author-time bugs surface immediately rather\n * than mid-run.\n *\n * Validation rules:\n * - Every value must be an agent, a workflow, a callback function,\n * or an object entry with `agent` / `workflow` / `run`.\n * - Object entries with more than one of `{ agent, workflow, run }`\n * throw with code `SUPERVISOR_INTENT_MIXED_DISPATCH`.\n * - Agent / workflow / agent-shaped entries must resolve to a\n * non-empty description from the underlying unit or the entry's\n * `description` override. Bare callback shorthand has no\n * description source — that's enforced separately by\n * {@link assertRouterDescriptions} when a router is configured.\n */\nexport function resolveIntentEntries(\n rawIntents: Record<string, SupervisorIntentValue>,\n supervisorName: string,\n): Map<string, ResolvedIntentEntry> {\n const entries = Object.entries(rawIntents);\n\n if (entries.length === 0) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): \\`intents\\` must contain at least one entry`,\n { context: { authoring: true } },\n );\n }\n\n const resolved = new Map<string, ResolvedIntentEntry>();\n\n for (const [intent, value] of entries) {\n if (!intent || typeof intent !== \"string\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): every \\`intents\\` key must be a non-empty string`,\n { context: { authoring: true } },\n );\n }\n\n resolved.set(intent, resolveOne(intent, value, supervisorName));\n }\n\n return resolved;\n}\n\n/**\n * Construction-time guard: when the supervisor is configured with a\n * `router`, every intent must resolve to a non-empty description so\n * the router LLM has a signal for picking it. Bare callback\n * shorthand and `IntentRunEntry` without `description` fail this\n * check; agents and workflows whose underlying primitive lacks a\n * description fail too — same uniform error message.\n *\n * Deterministic `route` callers skip this check entirely.\n */\nexport function assertRouterDescriptions(\n config: SupervisorConfig<unknown>,\n entries: Map<string, ResolvedIntentEntry>,\n): void {\n if (!config.router) {\n return;\n }\n\n for (const [intent, entry] of entries) {\n const description = entry.type === \"callback\" ? entry.description : entry.description;\n\n if (description && description.trim().length > 0) {\n continue;\n }\n\n const fix =\n entry.type === \"callback\"\n ? \"upgrade the bare callback to `{ run, description }`\"\n : \"set `description` on the agent/workflow or via the `IntentEntry` `description` override\";\n\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): intents[\"${intent}\"] needs a description because a \\`router\\` is configured — ${fix}`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_DESCRIPTION_REQUIRED\",\n );\n }\n}\n\nfunction resolveOne(\n intent: string,\n value: SupervisorIntentValue,\n supervisorName: string,\n): ResolvedIntentEntry {\n // (c) Bare callback shorthand — typeof function. Highest priority\n // so a user passing `(ctx) => …` never accidentally matches the\n // object-shape branches below.\n if (typeof value === \"function\") {\n return {\n intent,\n type: \"callback\",\n callback: value as IntentCallback,\n description: undefined,\n };\n }\n\n if (!value || typeof value !== \"object\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] is not an agent, workflow, callback, or entry object`,\n { context: { authoring: true, intent } },\n );\n }\n\n // Detect mixed-dispatch entries up front. Two of `{ agent, workflow,\n // run }` together is dev confusion, not a feature.\n assertSingleDispatchField(intent, value, supervisorName);\n\n // (d.run) Run-entry — `{ run, description?, input?, output? }`.\n if (\"run\" in value && typeof (value as IntentRunEntry).run === \"function\") {\n const entry = value as IntentRunEntry;\n\n return {\n intent,\n type: \"callback\",\n callback: entry.run,\n description: entry.description,\n input: entry.input,\n placeholders: entry.placeholders,\n output: entry.output,\n next: entry.next,\n };\n }\n\n // (d.agent / a / b) Agent-entry or bare unit. The existing\n // `IntentEntry` shape uses `agent: AgentContract | WorkflowInstance`\n // for both agent and workflow object entries; the resolver still\n // dispatches the underlying unit kind correctly.\n const entryForm = asAgentEntryForm(value);\n const unit = entryForm\n ? entryForm.agent\n : (value as AgentContract<unknown> | WorkflowInstance<unknown, unknown>);\n\n if (!isDispatchableUnit(unit)) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] must be an AgentContract, WorkflowInstance, callback, or entry object`,\n { context: { authoring: true, intent } },\n );\n }\n\n const detectedType = detectType(unit);\n const description = resolveAgentLikeDescription(intent, entryForm, unit, supervisorName);\n\n if (detectedType === \"workflow\") {\n if (entryForm?.mode === \"stream\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets \\`mode: \"stream\"\\` on a workflow entry — stream mode is agent-only in v1. Wrap the workflow in an agent or remove the \\`mode\\` field.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_ON_WORKFLOW\",\n );\n }\n\n return {\n intent,\n type: \"workflow\",\n unit: unit as WorkflowInstance<unknown, unknown>,\n description,\n input: entryForm?.input,\n placeholders: entryForm?.placeholders,\n output: entryForm?.output,\n next: entryForm?.next,\n history: entryForm?.history,\n };\n }\n\n assertStreamModeShape(intent, entryForm, supervisorName);\n\n return {\n intent,\n type: \"agent\",\n unit: unit as AgentContract<unknown>,\n description,\n input: entryForm?.input,\n placeholders: entryForm?.placeholders,\n output: entryForm?.output,\n next: entryForm?.next,\n history: entryForm?.history,\n mode: entryForm?.mode,\n streamTo: entryForm?.streamTo,\n };\n}\n\n/**\n * Phase 5 / decisions §34 — enforce the two stream-mode invariants at\n * construction time:\n *\n * 1. `mode: \"stream\"` and per-intent `output` are mutually exclusive.\n * Stream agents declare their state contribution via `streamTo`,\n * not via a schema; allowing both would silently pick one and\n * surprise the author.\n * 2. `streamTo` is required when `mode === \"stream\"`. A stream agent\n * that doesn't write somewhere is a black box — fail loud at the\n * factory rather than at run-time when state validation surfaces a\n * missing key.\n */\nfunction assertStreamModeShape(\n intent: string,\n entryForm: IntentEntry | undefined,\n supervisorName: string,\n): void {\n if (!entryForm || entryForm.mode !== \"stream\") {\n return;\n }\n\n if (entryForm.output) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets both \\`mode: \"stream\"\\` and \\`output\\` — stream mode declares its slice via \\`streamTo\\`, not a schema. Drop one.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_AND_OUTPUT\",\n );\n }\n\n if (typeof entryForm.streamTo !== \"string\" || entryForm.streamTo.trim().length === 0) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets \\`mode: \"stream\"\\` without a non-empty \\`streamTo\\` — a stream agent must name the state key its assembled prose writes into.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_TO_REQUIRED\",\n );\n }\n}\n\n/**\n * Reject entries that mix dispatch fields. `{ agent, run }` is a\n * common copy-paste bug; we surface it at construction with a clear\n * message rather than silently picking one based on resolution\n * order.\n */\nfunction assertSingleDispatchField(intent: string, value: object, supervisorName: string): void {\n const dispatchKeys = ([\"run\", \"agent\", \"workflow\"] as const).filter((key) => key in value);\n\n if (dispatchKeys.length > 1) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] has multiple dispatch fields (${dispatchKeys\n .map((key) => `\\`${key}\\``)\n .join(\n \", \",\n )}) — pick one. Two dispatch fields on the same entry is dev confusion, not a feature.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_MIXED_DISPATCH\",\n );\n }\n}\n\n/**\n * Coerce a `SupervisorIntentValue` into the agent-flavored\n * `IntentEntry` form when the caller passed the object form. Returns\n * `undefined` for bare shorthand. The shape check keys on the\n * presence of an `agent` property because both `AgentContract` and\n * `WorkflowInstance` have their own identifying fields\n * (`isAnonymous` for agents, `signature` for workflows) but neither\n * carries a top-level `agent`.\n */\nfunction asAgentEntryForm(value: object): IntentEntry | undefined {\n if (!(\"agent\" in value)) {\n return undefined;\n }\n\n const candidate = (value as { agent: unknown }).agent;\n\n if (!candidate || typeof candidate !== \"object\") {\n return undefined;\n }\n\n return value as IntentEntry;\n}\n\nfunction isDispatchableUnit(\n value: unknown,\n): value is AgentContract<unknown> | WorkflowInstance<unknown, unknown> {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as { name?: unknown; execute?: unknown };\n\n return typeof candidate.name === \"string\" && typeof candidate.execute === \"function\";\n}\n\nfunction detectType(\n unit: AgentContract<unknown> | WorkflowInstance<unknown, unknown>,\n): \"agent\" | \"workflow\" {\n // Workflows expose a structural `signature` field; agents don't.\n if (typeof (unit as WorkflowInstance<unknown, unknown>).signature === \"string\") {\n return \"workflow\";\n }\n\n return \"agent\";\n}\n\nfunction resolveAgentLikeDescription(\n intent: string,\n entryForm: IntentEntry | undefined,\n unit: AgentContract<unknown> | WorkflowInstance<unknown, unknown>,\n supervisorName: string,\n): string {\n const entryOverride = entryForm?.description;\n\n if (entryOverride && entryOverride.trim().length > 0) {\n return entryOverride;\n }\n\n const unitDescription = (unit as { description?: unknown }).description;\n\n if (typeof unitDescription === \"string\" && unitDescription.trim().length > 0) {\n return unitDescription;\n }\n\n // Empty string sentinel — caller (assertRouterDescriptions) decides\n // whether a missing description is fatal. Under deterministic\n // `route` mode it isn't.\n return \"\";\n}\n\n/**\n * Type guard helper for downstream modules. Narrows a raw\n * `AgentResult | WorkflowResult` based on the resolved entry's kind,\n * so transformers and emitters can pull the right fields without\n * re-checking shape.\n */\nexport function isAgentResult(raw: DispatchRawResult): raw is AgentResult<unknown> {\n return raw.type === \"agent\";\n}\n\nexport function isWorkflowResult(raw: DispatchRawResult): raw is WorkflowResult<unknown> {\n return raw.type === \"workflow\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"entries.mjs","names":[],"sources":["../../../../../../../../../@warlock.js/ai/src/supervisor/entries.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { AgentContract } from \"../contracts/agent/agent.contract\";\nimport type { Message } from \"../contracts/conversation-message.type\";\nimport type { EndSentinel } from \"../contracts/end.type\";\nimport type { AgentResult } from \"../contracts/result/agent-result.type\";\nimport type { WorkflowResult } from \"../contracts/result/workflow-result.type\";\nimport type { DispatchContext } from \"../contracts/supervisor/dispatch-context.type\";\nimport type {\n DispatchRawResult,\n IntentCallback,\n IntentEntry,\n IntentRunEntry,\n SupervisorIntentValue,\n} from \"../contracts/supervisor/intent-entry.type\";\nimport type { RouteContext } from \"../contracts/supervisor/route-context.type\";\nimport type { SupervisorConfig } from \"../contracts/supervisor/supervisor-config.type\";\nimport type { WorkflowInstance } from \"../contracts/workflow/workflow.contract\";\nimport { SupervisorFailedError } from \"../errors\";\n\n/**\n * Normalized internal representation of one entry in a supervisor's\n * `intents` map — resolved at factory time from one of the accepted\n * value forms (bare agent / workflow / callback / object entry).\n *\n * Carrying the explicit `type` discriminator keeps downstream code\n * (execution, signature, router-prompt) from having to re-detect\n * shape on every dispatch. The discriminated union below replaces\n * the flat-shape used in Phase 3 so callbacks can carry their own\n * function reference + dispatch-context-shaped resolvers.\n *\n * Discriminator renamed `kind` → `type` (Q12) for codebase-wide\n * consistency — every other discriminated result/report shape uses\n * `type`.\n */\nexport type ResolvedIntentEntry =\n | ResolvedAgentEntry\n | ResolvedWorkflowEntry\n | ResolvedCallbackEntry;\n\n/**\n * Successor directive function type — the resolver-time projection of\n * `IntentEntry.next` / `IntentRunEntry.next`. Single source of truth\n * across the three resolved variants.\n */\nexport type IntentNext = (ctx: DispatchContext) => string | string[] | EndSentinel | undefined;\n\n/**\n * Resolver-time projection of `IntentEntry.history` /\n * `RouterEntry.history` / `AckEntry.history`. Custom slicer that\n * REPLACES the default `historyWindow.<role>` slice.\n */\nexport type EntryHistorySlicer = (ctx: RouteContext) => Message[] | ReadonlyArray<Message>;\n\nexport type ResolvedAgentEntry = {\n intent: string;\n type: \"agent\";\n unit: AgentContract<unknown>;\n description: string;\n input?: (ctx: RouteContext) => string;\n /**\n * Per-dispatch placeholder values for the agent's systemPrompt\n * template. Forwarded as `agent.execute(input, { placeholders })`.\n * Phase 3.4 (Stage 4b) — replaces the dropped `composeAgentInput`\n * mechanism for threading state into agents.\n */\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n /**\n * Schema declaring this intent's slice of supervisor state. Agent\n * output is strip-merged against it; only validated keys appear on\n * `IterationSnapshot.result[intent].output` AND merge into\n * supervisor `state`.\n */\n output?: StandardSchemaV1<unknown>;\n /**\n * Successor directive (Stage 4d / Q24). When present, runs after\n * this branch's slice merges into state to choose the next dispatch\n * (or terminate) without invoking the router.\n */\n next?: IntentNext;\n /**\n * Custom history slicer — replaces the default\n * `historyWindow.agents` slice when supplied. See `IntentEntry.history`.\n */\n history?: EntryHistorySlicer;\n /**\n * Phase 5 / decisions §34. `\"stream\"` runs the agent without\n * structured-output coercion and writes the assembled prose into\n * `state[streamTo]`; `\"structured\"` is the default. Resolved at\n * factory time — `undefined` here is treated as `\"structured\"`.\n */\n mode?: \"structured\" | \"stream\";\n /** State key the assembled stream-mode prose writes into. Set iff `mode === \"stream\"`. */\n streamTo?: string;\n};\n\nexport type ResolvedWorkflowEntry = {\n intent: string;\n type: \"workflow\";\n unit: WorkflowInstance<unknown, unknown>;\n description: string;\n input?: (ctx: RouteContext) => string;\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n output?: StandardSchemaV1<unknown>;\n next?: IntentNext;\n history?: EntryHistorySlicer;\n};\n\nexport type ResolvedCallbackEntry = {\n intent: string;\n type: \"callback\";\n /**\n * The callback that actually runs at dispatch time. Always present\n * regardless of whether the user passed bare-function shorthand or\n * the `{ run, ... }` entry form.\n */\n callback: IntentCallback;\n /**\n * Description is required only when the supervisor uses a router.\n * Callback intents under a router are validated separately\n * (see {@link assertRouterDescriptions}); under deterministic\n * `route` mode this field is `undefined`.\n */\n description?: string;\n /**\n * Per-intent input resolver. Receives the upcoming\n * `DispatchContext` and returns the value forwarded as\n * `ctx.input` to the callback.\n */\n input?: (ctx: DispatchContext) => unknown;\n placeholders?: (ctx: DispatchContext) => Record<string, unknown>;\n /**\n * Schema declaring this callback's slice of state. Without it, the\n * full return value shallow-merges; with it, return is strip-merged\n * to declared keys before merging.\n */\n output?: StandardSchemaV1<unknown>;\n next?: IntentNext;\n};\n\n/**\n * Validate and normalize the `intents` map into resolved entries.\n * Runs at factory time — throws `SupervisorFailedError` on the first\n * malformed entry so author-time bugs surface immediately rather\n * than mid-run.\n *\n * Validation rules:\n * - Every value must be an agent, a workflow, a callback function,\n * or an object entry with `agent` / `workflow` / `run`.\n * - Object entries with more than one of `{ agent, workflow, run }`\n * throw with code `SUPERVISOR_INTENT_MIXED_DISPATCH`.\n * - Agent / workflow / agent-shaped entries must resolve to a\n * non-empty description from the underlying unit or the entry's\n * `description` override. Bare callback shorthand has no\n * description source — that's enforced separately by\n * {@link assertRouterDescriptions} when a router is configured.\n */\nexport function resolveIntentEntries(\n rawIntents: Record<string, SupervisorIntentValue>,\n supervisorName: string,\n): Map<string, ResolvedIntentEntry> {\n const entries = Object.entries(rawIntents);\n\n if (entries.length === 0) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): \\`intents\\` must contain at least one entry`,\n { context: { authoring: true } },\n );\n }\n\n const resolved = new Map<string, ResolvedIntentEntry>();\n\n for (const [intent, value] of entries) {\n if (!intent || typeof intent !== \"string\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): every \\`intents\\` key must be a non-empty string`,\n { context: { authoring: true } },\n );\n }\n\n resolved.set(intent, resolveOne(intent, value, supervisorName));\n }\n\n return resolved;\n}\n\n/**\n * Construction-time guard: when the supervisor is configured with a\n * `router`, every intent must resolve to a non-empty description so\n * the router LLM has a signal for picking it. Bare callback\n * shorthand and `IntentRunEntry` without `description` fail this\n * check; agents and workflows whose underlying primitive lacks a\n * description fail too — same uniform error message.\n *\n * Deterministic `route` callers skip this check entirely.\n */\nexport function assertRouterDescriptions(\n config: SupervisorConfig<unknown>,\n entries: Map<string, ResolvedIntentEntry>,\n): void {\n if (!config.router) {\n return;\n }\n\n for (const [intent, entry] of entries) {\n const description = entry.type === \"callback\" ? entry.description : entry.description;\n\n if (description && description.trim().length > 0) {\n continue;\n }\n\n const fix =\n entry.type === \"callback\"\n ? \"upgrade the bare callback to `{ run, description }`\"\n : \"set `description` on the agent/workflow or via the `IntentEntry` `description` override\";\n\n throw new SupervisorFailedError(\n `ai.supervisor(\"${config.name}\"): intents[\"${intent}\"] needs a description because a \\`router\\` is configured — ${fix}`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_DESCRIPTION_REQUIRED\",\n );\n }\n}\n\nfunction resolveOne(\n intent: string,\n value: SupervisorIntentValue,\n supervisorName: string,\n): ResolvedIntentEntry {\n // (c) Bare callback shorthand — typeof function. Highest priority\n // so a user passing `(ctx) => …` never accidentally matches the\n // object-shape branches below.\n if (typeof value === \"function\") {\n return {\n intent,\n type: \"callback\",\n callback: value as IntentCallback,\n description: undefined,\n };\n }\n\n if (!value || typeof value !== \"object\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] is not an agent, workflow, callback, or entry object`,\n { context: { authoring: true, intent } },\n );\n }\n\n // Detect mixed-dispatch entries up front. Two of `{ agent, workflow,\n // run }` together is dev confusion, not a feature.\n assertSingleDispatchField(intent, value, supervisorName);\n\n // (d.run) Run-entry — `{ run, description?, input?, output? }`.\n if (\"run\" in value && typeof (value as IntentRunEntry).run === \"function\") {\n const entry = value as IntentRunEntry;\n\n return {\n intent,\n type: \"callback\",\n callback: entry.run,\n description: entry.description,\n input: entry.input,\n placeholders: entry.placeholders,\n output: entry.output,\n next: entry.next,\n };\n }\n\n // (d.agent / a / b) Agent-entry or bare unit. The existing\n // `IntentEntry` shape uses `agent: AgentContract | WorkflowInstance`\n // for both agent and workflow object entries; the resolver still\n // dispatches the underlying unit kind correctly.\n const entryForm = asAgentEntryForm(value);\n const unit = entryForm\n ? entryForm.agent\n : (value as AgentContract<unknown> | WorkflowInstance<unknown, unknown>);\n\n if (!isDispatchableUnit(unit)) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] must be an AgentContract, WorkflowInstance, callback, or entry object`,\n { context: { authoring: true, intent } },\n );\n }\n\n const detectedType = detectType(unit);\n const description = resolveAgentLikeDescription(intent, entryForm, unit, supervisorName);\n\n if (detectedType === \"workflow\") {\n if (entryForm?.mode === \"stream\") {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets \\`mode: \"stream\"\\` on a workflow entry — stream mode is agent-only in v1. Wrap the workflow in an agent or remove the \\`mode\\` field.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_ON_WORKFLOW\",\n );\n }\n\n return {\n intent,\n type: \"workflow\",\n unit: unit as WorkflowInstance<unknown, unknown>,\n description,\n input: entryForm?.input,\n placeholders: entryForm?.placeholders,\n output: entryForm?.output,\n next: entryForm?.next,\n history: entryForm?.history,\n };\n }\n\n assertStreamModeShape(intent, entryForm, supervisorName);\n\n return {\n intent,\n type: \"agent\",\n unit: unit as AgentContract<unknown>,\n description,\n input: entryForm?.input,\n placeholders: entryForm?.placeholders,\n output: entryForm?.output,\n next: entryForm?.next,\n history: entryForm?.history,\n mode: entryForm?.mode,\n streamTo: entryForm?.streamTo,\n };\n}\n\n/**\n * Phase 5 / decisions §34 — enforce the two stream-mode invariants at\n * construction time:\n *\n * 1. `mode: \"stream\"` and per-intent `output` are mutually exclusive.\n * Stream agents declare their state contribution via `streamTo`,\n * not via a schema; allowing both would silently pick one and\n * surprise the author.\n * 2. `streamTo` is required when `mode === \"stream\"`. A stream agent\n * that doesn't write somewhere is a black box — fail loud at the\n * factory rather than at run-time when state validation surfaces a\n * missing key.\n */\nfunction assertStreamModeShape(\n intent: string,\n entryForm: IntentEntry | undefined,\n supervisorName: string,\n): void {\n if (!entryForm || entryForm.mode !== \"stream\") {\n return;\n }\n\n if (entryForm.output) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets both \\`mode: \"stream\"\\` and \\`output\\` — stream mode declares its slice via \\`streamTo\\`, not a schema. Drop one.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_AND_OUTPUT\",\n );\n }\n\n if (typeof entryForm.streamTo !== \"string\" || entryForm.streamTo.trim().length === 0) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] sets \\`mode: \"stream\"\\` without a non-empty \\`streamTo\\` — a stream agent must name the state key its assembled prose writes into.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_STREAM_TO_REQUIRED\",\n );\n }\n}\n\n/**\n * Reject entries that mix dispatch fields. `{ agent, run }` is a\n * common copy-paste bug; we surface it at construction with a clear\n * message rather than silently picking one based on resolution\n * order.\n */\nfunction assertSingleDispatchField(intent: string, value: object, supervisorName: string): void {\n const dispatchKeys = ([\"run\", \"agent\", \"workflow\"] as const).filter((key) => key in value);\n\n if (dispatchKeys.length > 1) {\n throw new SupervisorFailedError(\n `ai.supervisor(\"${supervisorName}\"): intents[\"${intent}\"] has multiple dispatch fields (${dispatchKeys\n .map((key) => `\\`${key}\\``)\n .join(\n \", \",\n )}) — pick one. Two dispatch fields on the same entry is dev confusion, not a feature.`,\n { context: { authoring: true, intent } },\n \"SUPERVISOR_INTENT_MIXED_DISPATCH\",\n );\n }\n}\n\n/**\n * Coerce a `SupervisorIntentValue` into the agent-flavored\n * `IntentEntry` form when the caller passed the object form. Returns\n * `undefined` for bare shorthand. The shape check keys on the\n * presence of an `agent` property because both `AgentContract` and\n * `WorkflowInstance` have their own identifying fields\n * (`isAnonymous` for agents, `signature` for workflows) but neither\n * carries a top-level `agent`.\n */\nfunction asAgentEntryForm(value: object): IntentEntry | undefined {\n if (!(\"agent\" in value)) {\n return undefined;\n }\n\n const candidate = (value as { agent: unknown }).agent;\n\n if (!candidate || typeof candidate !== \"object\") {\n return undefined;\n }\n\n return value as IntentEntry;\n}\n\nfunction isDispatchableUnit(\n value: unknown,\n): value is AgentContract<unknown> | WorkflowInstance<unknown, unknown> {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as { name?: unknown; execute?: unknown };\n\n return typeof candidate.name === \"string\" && typeof candidate.execute === \"function\";\n}\n\nfunction detectType(\n unit: AgentContract<unknown> | WorkflowInstance<unknown, unknown>,\n): \"agent\" | \"workflow\" {\n // Both agents and workflows now expose a structural `signature` (the\n // drift fingerprint durable resume added to the agent), so `signature`\n // no longer distinguishes them. Agents expose a token-`stream()` method;\n // workflows do not (workflow streaming is step-level, not a `.stream`\n // API) — use that as the positive agent marker.\n if (typeof (unit as AgentContract<unknown>).stream === \"function\") {\n return \"agent\";\n }\n\n return \"workflow\";\n}\n\nfunction resolveAgentLikeDescription(\n intent: string,\n entryForm: IntentEntry | undefined,\n unit: AgentContract<unknown> | WorkflowInstance<unknown, unknown>,\n supervisorName: string,\n): string {\n const entryOverride = entryForm?.description;\n\n if (entryOverride && entryOverride.trim().length > 0) {\n return entryOverride;\n }\n\n const unitDescription = (unit as { description?: unknown }).description;\n\n if (typeof unitDescription === \"string\" && unitDescription.trim().length > 0) {\n return unitDescription;\n }\n\n // Empty string sentinel — caller (assertRouterDescriptions) decides\n // whether a missing description is fatal. Under deterministic\n // `route` mode it isn't.\n return \"\";\n}\n\n/**\n * Type guard helper for downstream modules. Narrows a raw\n * `AgentResult | WorkflowResult` based on the resolved entry's kind,\n * so transformers and emitters can pull the right fields without\n * re-checking shape.\n */\nexport function isAgentResult(raw: DispatchRawResult): raw is AgentResult<unknown> {\n return raw.type === \"agent\";\n}\n\nexport function isWorkflowResult(raw: DispatchRawResult): raw is WorkflowResult<unknown> {\n return raw.type === \"workflow\";\n}\n"],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { basename, extname } from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ //#region ../@warlock.js/ai/src/transcribe/audio-input.ts
5
+ /**
6
+ * File-extension → IANA audio media type map covering the formats the
7
+ * common STT providers accept — including the **WhatsApp voice-note**
8
+ * formats (`.ogg` / `.opus`, Opus-in-Ogg on Android; `.m4a` on iOS).
9
+ */
10
+ const AUDIO_MEDIA_TYPES = {
11
+ ".mp3": "audio/mpeg",
12
+ ".mpeg": "audio/mpeg",
13
+ ".mpga": "audio/mpeg",
14
+ ".m4a": "audio/mp4",
15
+ ".mp4": "audio/mp4",
16
+ ".wav": "audio/wav",
17
+ ".webm": "audio/webm",
18
+ ".weba": "audio/webm",
19
+ ".ogg": "audio/ogg",
20
+ ".oga": "audio/ogg",
21
+ ".opus": "audio/ogg",
22
+ ".flac": "audio/flac",
23
+ ".aac": "audio/aac"
24
+ };
25
+ /**
26
+ * Resolve the audio media type from a filename's extension, or
27
+ * `undefined` when the extension is unknown. Case-insensitive.
28
+ *
29
+ * @example
30
+ * audioMediaTypeForFilename("voice-note.opus"); // "audio/ogg"
31
+ */
32
+ function audioMediaTypeForFilename(filename) {
33
+ return AUDIO_MEDIA_TYPES[extname(filename).toLowerCase()];
34
+ }
35
+ /**
36
+ * Package raw audio bytes as an {@link AudioInput} for `ai.transcribe()`.
37
+ * Pure plumbing — no AI, no I/O. Use when you already hold the bytes
38
+ * (an upload buffer, a downloaded blob).
39
+ *
40
+ * @example
41
+ * const audio = audioFromBuffer(uploadBuffer, "audio/ogg", "note.ogg");
42
+ * const { data } = await ai.transcribe({ model: openai.transcribe({ name: "whisper-1" }), audio });
43
+ */
44
+ function audioFromBuffer(data, mediaType, filename) {
45
+ return {
46
+ base64: Buffer.from(data).toString("base64"),
47
+ mediaType,
48
+ ...filename ? { filename } : {}
49
+ };
50
+ }
51
+ /**
52
+ * Read an audio file from disk and package it as an {@link AudioInput}
53
+ * for `ai.transcribe()` — the one-line bridge from a file on disk
54
+ * (WhatsApp `.ogg`/`.opus`, a meeting `.m4a`, a `.wav`) to the
55
+ * transcription verb. **Pure utility — no AI here**; the actual text
56
+ * extraction is the AI step (`ai.transcribe`).
57
+ *
58
+ * The media type is inferred from the file extension (override via
59
+ * `options.mediaType` for extensionless or mislabeled files).
60
+ *
61
+ * @example
62
+ * // WhatsApp voice note → text, end to end:
63
+ * const audio = await audioFromFile("./voice-note.ogg");
64
+ * const { data, error } = await ai.transcribe({
65
+ * model: openai.transcribe({ name: "whisper-1" }),
66
+ * audio,
67
+ * language: "en",
68
+ * });
69
+ * if (!error) console.log(data.text);
70
+ */
71
+ async function audioFromFile(filePath, options) {
72
+ const buffer = await readFile(filePath);
73
+ const filename = basename(filePath);
74
+ const mediaType = options?.mediaType ?? audioMediaTypeForFilename(filename) ?? "audio/mpeg";
75
+ return {
76
+ base64: buffer.toString("base64"),
77
+ mediaType,
78
+ filename
79
+ };
80
+ }
81
+
82
+ //#endregion
83
+ export { audioFromBuffer, audioFromFile, audioMediaTypeForFilename };
84
+ //# sourceMappingURL=audio-input.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-input.mjs","names":[],"sources":["../../../../../../../../../@warlock.js/ai/src/transcribe/audio-input.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { AudioInput } from \"../contracts/transcription-model.contract\";\n\n/**\n * File-extension → IANA audio media type map covering the formats the\n * common STT providers accept — including the **WhatsApp voice-note**\n * formats (`.ogg` / `.opus`, Opus-in-Ogg on Android; `.m4a` on iOS).\n */\nconst AUDIO_MEDIA_TYPES: Record<string, string> = {\n \".mp3\": \"audio/mpeg\",\n \".mpeg\": \"audio/mpeg\",\n \".mpga\": \"audio/mpeg\",\n \".m4a\": \"audio/mp4\",\n \".mp4\": \"audio/mp4\",\n \".wav\": \"audio/wav\",\n \".webm\": \"audio/webm\",\n \".weba\": \"audio/webm\",\n \".ogg\": \"audio/ogg\",\n \".oga\": \"audio/ogg\",\n \".opus\": \"audio/ogg\",\n \".flac\": \"audio/flac\",\n \".aac\": \"audio/aac\",\n};\n\n/**\n * Resolve the audio media type from a filename's extension, or\n * `undefined` when the extension is unknown. Case-insensitive.\n *\n * @example\n * audioMediaTypeForFilename(\"voice-note.opus\"); // \"audio/ogg\"\n */\nexport function audioMediaTypeForFilename(filename: string): string | undefined {\n return AUDIO_MEDIA_TYPES[extname(filename).toLowerCase()];\n}\n\n/**\n * Package raw audio bytes as an {@link AudioInput} for `ai.transcribe()`.\n * Pure plumbing — no AI, no I/O. Use when you already hold the bytes\n * (an upload buffer, a downloaded blob).\n *\n * @example\n * const audio = audioFromBuffer(uploadBuffer, \"audio/ogg\", \"note.ogg\");\n * const { data } = await ai.transcribe({ model: openai.transcribe({ name: \"whisper-1\" }), audio });\n */\nexport function audioFromBuffer(\n data: Uint8Array,\n mediaType: string,\n filename?: string,\n): AudioInput {\n return {\n base64: Buffer.from(data).toString(\"base64\"),\n mediaType,\n ...(filename ? { filename } : {}),\n };\n}\n\n/**\n * Read an audio file from disk and package it as an {@link AudioInput}\n * for `ai.transcribe()` — the one-line bridge from a file on disk\n * (WhatsApp `.ogg`/`.opus`, a meeting `.m4a`, a `.wav`) to the\n * transcription verb. **Pure utility — no AI here**; the actual text\n * extraction is the AI step (`ai.transcribe`).\n *\n * The media type is inferred from the file extension (override via\n * `options.mediaType` for extensionless or mislabeled files).\n *\n * @example\n * // WhatsApp voice note → text, end to end:\n * const audio = await audioFromFile(\"./voice-note.ogg\");\n * const { data, error } = await ai.transcribe({\n * model: openai.transcribe({ name: \"whisper-1\" }),\n * audio,\n * language: \"en\",\n * });\n * if (!error) console.log(data.text);\n */\nexport async function audioFromFile(\n filePath: string,\n options?: { mediaType?: string },\n): Promise<AudioInput> {\n const buffer = await readFile(filePath);\n const filename = basename(filePath);\n const mediaType = options?.mediaType ?? audioMediaTypeForFilename(filename) ?? \"audio/mpeg\";\n\n return { base64: buffer.toString(\"base64\"), mediaType, filename };\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,oBAA4C;CAChD,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;AACV;;;;;;;;AASA,SAAgB,0BAA0B,UAAsC;CAC9E,OAAO,kBAAkB,QAAQ,QAAQ,CAAC,CAAC,YAAY;AACzD;;;;;;;;;;AAWA,SAAgB,gBACd,MACA,WACA,UACY;CACZ,OAAO;EACL,QAAQ,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,QAAQ;EAC3C;EACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,cACpB,UACA,SACqB;CACrB,MAAM,SAAS,MAAM,SAAS,QAAQ;CACtC,MAAM,WAAW,SAAS,QAAQ;CAClC,MAAM,YAAY,SAAS,aAAa,0BAA0B,QAAQ,KAAK;CAE/E,OAAO;EAAE,QAAQ,OAAO,SAAS,QAAQ;EAAG;EAAW;CAAS;AAClE"}
@@ -0,0 +1,4 @@
1
+ import { audioFromBuffer, audioFromFile, audioMediaTypeForFilename } from "./audio-input.mjs";
2
+ import { transcribe } from "./transcribe.mjs";
3
+
4
+ export { };
@@ -0,0 +1,127 @@
1
+ import { AIError } from "../errors/ai-error.mjs";
2
+ import { ProviderError } from "../errors/provider-error.mjs";
3
+ import { computeCost } from "../utils/compute-cost.mjs";
4
+ import { generateRunId } from "../utils/generate-run-id.mjs";
5
+ import { stampReportLineage } from "../utils/stamp-report-lineage.mjs";
6
+ import { notifyObservers } from "../observe/resolve-observers.mjs";
7
+
8
+ //#region ../@warlock.js/ai/src/transcribe/transcribe.ts
9
+ /**
10
+ * Transcribe audio to text — the speech-to-text verb of the
11
+ * output-modality track (Theme I), inverse of `ai.speech()`. Wraps a
12
+ * {@link TranscriptionModelContract} (from `openai.transcribe(...)`) in
13
+ * the uniform result contract:
14
+ *
15
+ * - **Never throws.** Provider failures surface as a typed `AIError` on
16
+ * `result.error`.
17
+ * - **Cost-truth.** `result.usage.cost` is filled per-minute
18
+ * (`whisper-1`) or per-token (`gpt-4o-transcribe`).
19
+ * - **Observable.** The completed {@link TranscriptionReport} routes to
20
+ * any registered `Observer` via the `observe` seam.
21
+ *
22
+ * @example
23
+ * const openai = new OpenAISDK({ apiKey });
24
+ * const { data, error } = await ai.transcribe({
25
+ * model: openai.transcribe({ name: "whisper-1" }),
26
+ * audio: { base64, mediaType: "audio/mpeg", filename: "voicemail.mp3" },
27
+ * language: "en",
28
+ * });
29
+ * if (!error) console.log(data.text);
30
+ */
31
+ async function transcribe(params) {
32
+ const { model, audio } = params;
33
+ const runId = generateRunId("transcription");
34
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
35
+ const startPerf = performance.now();
36
+ const usage = {
37
+ input: 0,
38
+ output: 0,
39
+ total: 0
40
+ };
41
+ let data;
42
+ let error;
43
+ let status = "completed";
44
+ let durationSeconds;
45
+ try {
46
+ const response = await model.transcribe(audio, {
47
+ language: params.language,
48
+ prompt: params.prompt,
49
+ format: params.format,
50
+ signal: params.signal,
51
+ ...params.options
52
+ });
53
+ Object.assign(usage, response.usage);
54
+ durationSeconds = response.durationSeconds;
55
+ if (usage.cost === void 0) {
56
+ const cost = computeTranscriptionCost(usage, durationSeconds, model.pricing);
57
+ if (cost !== void 0) usage.cost = cost;
58
+ }
59
+ data = {
60
+ text: response.text,
61
+ ...response.segments ? { segments: response.segments } : {}
62
+ };
63
+ } catch (thrown) {
64
+ error = thrown instanceof AIError ? thrown : new ProviderError(toMessage(thrown), { cause: thrown });
65
+ status = params.signal?.aborted ? "cancelled" : "failed";
66
+ }
67
+ const report = {
68
+ runId,
69
+ rootRunId: runId,
70
+ name: params.name ?? "transcription",
71
+ type: "transcription",
72
+ status,
73
+ error,
74
+ startedAt,
75
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
76
+ duration: performance.now() - startPerf,
77
+ usage,
78
+ children: [],
79
+ model: {
80
+ name: model.name,
81
+ provider: model.provider
82
+ },
83
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
84
+ reportSchemaVersion: 1
85
+ };
86
+ stampReportLineage(report, {
87
+ rootRunId: runId,
88
+ sessionId: params.sessionId
89
+ });
90
+ await notifyObservers(params.observe, report);
91
+ return {
92
+ type: "transcription",
93
+ data,
94
+ error,
95
+ usage,
96
+ report
97
+ };
98
+ }
99
+ /**
100
+ * Price an STT run: `perMinute × (durationSeconds / 60)` (per-minute
101
+ * metering, attributed to `cost.input`) wins when configured, otherwise
102
+ * the standard token math. Returns `undefined` when no usable pricing
103
+ * is present (e.g. per-minute pricing but the provider didn't report a
104
+ * duration).
105
+ */
106
+ function computeTranscriptionCost(usage, durationSeconds, pricing) {
107
+ if (!pricing) return;
108
+ if (pricing.perMinute !== void 0) {
109
+ if (durationSeconds === void 0) return;
110
+ return {
111
+ input: durationSeconds / 60 * pricing.perMinute,
112
+ output: 0
113
+ };
114
+ }
115
+ if (pricing.input !== void 0 && pricing.output !== void 0) return computeCost(usage, {
116
+ input: pricing.input,
117
+ output: pricing.output
118
+ });
119
+ }
120
+ /** Best-effort message for a non-`AIError` thrown value. */
121
+ function toMessage(thrown) {
122
+ return thrown instanceof Error ? thrown.message : String(thrown);
123
+ }
124
+
125
+ //#endregion
126
+ export { transcribe };
127
+ //# sourceMappingURL=transcribe.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcribe.mjs","names":[],"sources":["../../../../../../../../../@warlock.js/ai/src/transcribe/transcribe.ts"],"sourcesContent":["import type { BaseReport } from \"../contracts/result/base-report.type\";\nimport { REPORT_SCHEMA_VERSION } from \"../contracts/result/base-report.type\";\nimport type { ExecuteResult } from \"../contracts/result/execute-result.type\";\nimport type { ModelPricing } from \"../contracts/result/model-pricing.type\";\nimport type { Usage } from \"../contracts/result/usage.type\";\nimport type {\n AudioInput,\n TranscriptionModelContract,\n TranscriptionModelPricing,\n TranscriptionSegment,\n} from \"../contracts/transcription-model.contract\";\nimport { AIError } from \"../errors/ai-error\";\nimport { ProviderError } from \"../errors/provider-error\";\nimport type { FlowObserveOption } from \"../observe/resolve-observers\";\nimport { notifyObservers } from \"../observe/resolve-observers\";\nimport { computeCost } from \"../utils/compute-cost\";\nimport { generateRunId } from \"../utils/generate-run-id\";\nimport { stampReportLineage } from \"../utils/stamp-report-lineage\";\n\n/** Parameters for {@link transcribe}. `model` comes from `sdk.transcribe({ name })`. */\nexport type TranscribeParams = {\n /** The STT model to transcribe with. */\n model: TranscriptionModelContract;\n /** The audio to transcribe (inlined base64 bytes + media type). */\n audio: AudioInput;\n /** BCP-47 language hint. */\n language?: string;\n /** Optional priming prompt (spelling/style hints). */\n prompt?: string;\n /** Provider response-format override (e.g. `\"verbose_json\"`). */\n format?: string;\n /** Cancellation handle. */\n signal?: AbortSignal;\n /** Observability routing — same `observe` seam as agents. */\n observe?: FlowObserveOption;\n /** Groups this call into a session for flat cost/trace queries. */\n sessionId?: string;\n /** Report node name (defaults to `\"transcription\"`). */\n name?: string;\n /** Provider-specific options forwarded verbatim to the adapter. */\n options?: Record<string, unknown>;\n};\n\n/** Success payload of a {@link transcribe} run. */\nexport type TranscriptionData = {\n /** The full transcript text. */\n text: string;\n /** Timestamped segments when the provider returned them. */\n segments?: TranscriptionSegment[];\n};\n\n/** The report node a {@link transcribe} run produces (`type: \"transcription\"`). */\nexport type TranscriptionReport = BaseReport & {\n type: \"transcription\";\n /** Identity of the STT model this run used. */\n model: { name: string; provider: string };\n /** Input audio duration in seconds, when the provider reported it. */\n durationSeconds?: number;\n};\n\n/** Result envelope of {@link transcribe} — the uniform `{ data, error, usage, report }`. */\nexport type TranscriptionResult = ExecuteResult<TranscriptionData> & {\n type: \"transcription\";\n report: TranscriptionReport;\n};\n\n/**\n * Transcribe audio to text — the speech-to-text verb of the\n * output-modality track (Theme I), inverse of `ai.speech()`. Wraps a\n * {@link TranscriptionModelContract} (from `openai.transcribe(...)`) in\n * the uniform result contract:\n *\n * - **Never throws.** Provider failures surface as a typed `AIError` on\n * `result.error`.\n * - **Cost-truth.** `result.usage.cost` is filled per-minute\n * (`whisper-1`) or per-token (`gpt-4o-transcribe`).\n * - **Observable.** The completed {@link TranscriptionReport} routes to\n * any registered `Observer` via the `observe` seam.\n *\n * @example\n * const openai = new OpenAISDK({ apiKey });\n * const { data, error } = await ai.transcribe({\n * model: openai.transcribe({ name: \"whisper-1\" }),\n * audio: { base64, mediaType: \"audio/mpeg\", filename: \"voicemail.mp3\" },\n * language: \"en\",\n * });\n * if (!error) console.log(data.text);\n */\nexport async function transcribe(params: TranscribeParams): Promise<TranscriptionResult> {\n const { model, audio } = params;\n\n const runId = generateRunId(\"transcription\");\n const startedAt = new Date().toISOString();\n const startPerf = performance.now();\n\n const usage: Usage = { input: 0, output: 0, total: 0 };\n let data: TranscriptionData | undefined;\n let error: AIError | undefined;\n let status: TranscriptionReport[\"status\"] = \"completed\";\n let durationSeconds: number | undefined;\n\n try {\n const response = await model.transcribe(audio, {\n language: params.language,\n prompt: params.prompt,\n format: params.format,\n signal: params.signal,\n ...params.options,\n });\n\n Object.assign(usage, response.usage);\n durationSeconds = response.durationSeconds;\n\n if (usage.cost === undefined) {\n const cost = computeTranscriptionCost(usage, durationSeconds, model.pricing);\n if (cost !== undefined) {\n usage.cost = cost;\n }\n }\n\n data = { text: response.text, ...(response.segments ? { segments: response.segments } : {}) };\n } catch (thrown) {\n error =\n thrown instanceof AIError ? thrown : new ProviderError(toMessage(thrown), { cause: thrown });\n status = params.signal?.aborted ? \"cancelled\" : \"failed\";\n }\n\n const report: TranscriptionReport = {\n runId,\n rootRunId: runId,\n name: params.name ?? \"transcription\",\n type: \"transcription\",\n status,\n error,\n startedAt,\n endedAt: new Date().toISOString(),\n duration: performance.now() - startPerf,\n usage,\n children: [],\n model: { name: model.name, provider: model.provider },\n ...(durationSeconds !== undefined ? { durationSeconds } : {}),\n reportSchemaVersion: REPORT_SCHEMA_VERSION,\n };\n\n stampReportLineage(report, { rootRunId: runId, sessionId: params.sessionId });\n\n await notifyObservers(params.observe, report);\n\n return { type: \"transcription\", data, error, usage, report };\n}\n\n/**\n * Price an STT run: `perMinute × (durationSeconds / 60)` (per-minute\n * metering, attributed to `cost.input`) wins when configured, otherwise\n * the standard token math. Returns `undefined` when no usable pricing\n * is present (e.g. per-minute pricing but the provider didn't report a\n * duration).\n */\nfunction computeTranscriptionCost(\n usage: Usage,\n durationSeconds: number | undefined,\n pricing: TranscriptionModelPricing | undefined,\n): ModelPricing | undefined {\n if (!pricing) {\n return undefined;\n }\n\n if (pricing.perMinute !== undefined) {\n if (durationSeconds === undefined) {\n return undefined;\n }\n return { input: (durationSeconds / 60) * pricing.perMinute, output: 0 };\n }\n\n if (pricing.input !== undefined && pricing.output !== undefined) {\n return computeCost(usage, { input: pricing.input, output: pricing.output });\n }\n\n return undefined;\n}\n\n/** Best-effort message for a non-`AIError` thrown value. */\nfunction toMessage(thrown: unknown): string {\n return thrown instanceof Error ? thrown.message : String(thrown);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFA,eAAsB,WAAW,QAAwD;CACvF,MAAM,EAAE,OAAO,UAAU;CAEzB,MAAM,QAAQ,cAAc,eAAe;CAC3C,MAAM,6BAAY,IAAI,KAAK,EAAC,CAAC,YAAY;CACzC,MAAM,YAAY,YAAY,IAAI;CAElC,MAAM,QAAe;EAAE,OAAO;EAAG,QAAQ;EAAG,OAAO;CAAE;CACrD,IAAI;CACJ,IAAI;CACJ,IAAI,SAAwC;CAC5C,IAAI;CAEJ,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,WAAW,OAAO;GAC7C,UAAU,OAAO;GACjB,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,QAAQ,OAAO;GACf,GAAG,OAAO;EACZ,CAAC;EAED,OAAO,OAAO,OAAO,SAAS,KAAK;EACnC,kBAAkB,SAAS;EAE3B,IAAI,MAAM,SAAS,QAAW;GAC5B,MAAM,OAAO,yBAAyB,OAAO,iBAAiB,MAAM,OAAO;GAC3E,IAAI,SAAS,QACX,MAAM,OAAO;EAEjB;EAEA,OAAO;GAAE,MAAM,SAAS;GAAM,GAAI,SAAS,WAAW,EAAE,UAAU,SAAS,SAAS,IAAI,CAAC;EAAG;CAC9F,SAAS,QAAQ;EACf,QACE,kBAAkB,UAAU,SAAS,IAAI,cAAc,UAAU,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;EAC7F,SAAS,OAAO,QAAQ,UAAU,cAAc;CAClD;CAEA,MAAM,SAA8B;EAClC;EACA,WAAW;EACX,MAAM,OAAO,QAAQ;EACrB,MAAM;EACN;EACA;EACA;EACA,0BAAS,IAAI,KAAK,EAAC,CAAC,YAAY;EAChC,UAAU,YAAY,IAAI,IAAI;EAC9B;EACA,UAAU,CAAC;EACX,OAAO;GAAE,MAAM,MAAM;GAAM,UAAU,MAAM;EAAS;EACpD,GAAI,oBAAoB,SAAY,EAAE,gBAAgB,IAAI,CAAC;EAC3D;CACF;CAEA,mBAAmB,QAAQ;EAAE,WAAW;EAAO,WAAW,OAAO;CAAU,CAAC;CAE5E,MAAM,gBAAgB,OAAO,SAAS,MAAM;CAE5C,OAAO;EAAE,MAAM;EAAiB;EAAM;EAAO;EAAO;CAAO;AAC7D;;;;;;;;AASA,SAAS,yBACP,OACA,iBACA,SAC0B;CAC1B,IAAI,CAAC,SACH;CAGF,IAAI,QAAQ,cAAc,QAAW;EACnC,IAAI,oBAAoB,QACtB;EAEF,OAAO;GAAE,OAAQ,kBAAkB,KAAM,QAAQ;GAAW,QAAQ;EAAE;CACxE;CAEA,IAAI,QAAQ,UAAU,UAAa,QAAQ,WAAW,QACpD,OAAO,YAAY,OAAO;EAAE,OAAO,QAAQ;EAAO,QAAQ,QAAQ;CAAO,CAAC;AAI9E;;AAGA,SAAS,UAAU,QAAyB;CAC1C,OAAO,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AACjE"}
@@ -0,0 +1,5 @@
1
+ import "../../ai/src/index.mjs";
2
+ import "./utils/index.mjs";
3
+ import { log } from "@warlock.js/logger";
4
+
5
+ export { };
@@ -1,4 +1,7 @@
1
1
  import "./embedder.mjs";
2
+ import "./image.mjs";
3
+ import "./speech.mjs";
4
+ import "./transcription.mjs";
2
5
  import "./sdk.mjs";
3
6
 
4
7
  export { };
@@ -1,6 +1,9 @@
1
1
  import "../../node_modules/openai/index.mjs";
2
2
  import "../../ai/src/index.mjs";
3
3
  import "./embedder.mjs";
4
+ import "./image.mjs";
4
5
  import "./model.mjs";
6
+ import "./speech.mjs";
7
+ import "./transcription.mjs";
5
8
 
6
9
  export { };
@@ -0,0 +1,5 @@
1
+ import "../../ai/src/index.mjs";
2
+ import "./utils/index.mjs";
3
+ import { log } from "@warlock.js/logger";
4
+
5
+ export { };
@@ -0,0 +1,6 @@
1
+ import "../../node_modules/openai/index.mjs";
2
+ import "../../ai/src/index.mjs";
3
+ import "./utils/index.mjs";
4
+ import { log } from "@warlock.js/logger";
5
+
6
+ export { };
@@ -1,3 +1,4 @@
1
+ import "./to-openai-messages.mjs";
1
2
  import "./to-openai-tools.mjs";
2
3
  import "./wrap-openai-error.mjs";
3
4
 
@@ -0,0 +1,3 @@
1
+ import "../../../ai/src/index.mjs";
2
+
3
+ export { };
@@ -0,0 +1,42 @@
1
+ import { config } from "../../../../config/config-getter.mjs";
2
+
3
+ //#region ../@warlock.js/core/src/cli/commands/doctor/checks/config.check.ts
4
+ /**
5
+ * Config sections that a typical Warlock HTTP application is expected to
6
+ * register. A missing section here is release/runtime-blocking, so absence is
7
+ * a `fail` rather than a warning.
8
+ */
9
+ const REQUIRED_CONFIG_SECTIONS = ["app", "http"];
10
+ /**
11
+ * `@mongez/config` returns `null` (not `undefined`) for an unregistered key,
12
+ * so treat both nullish values as "missing".
13
+ */
14
+ function isPresent(value) {
15
+ return value !== null && value !== void 0;
16
+ }
17
+ /**
18
+ * Verifies that the required top-level config sections are present. Reads each
19
+ * section via `config.get` only — never mutates configuration.
20
+ */
21
+ const configCheck = {
22
+ name: "config",
23
+ run: () => {
24
+ const missing = REQUIRED_CONFIG_SECTIONS.filter((section) => {
25
+ return !isPresent(config.get(section));
26
+ });
27
+ if (missing.length > 0) return {
28
+ name: "config",
29
+ status: "fail",
30
+ detail: `missing required config section(s): ${missing.join(", ")}`
31
+ };
32
+ return {
33
+ name: "config",
34
+ status: "ok",
35
+ detail: `required sections present (${REQUIRED_CONFIG_SECTIONS.join(", ")})`
36
+ };
37
+ }
38
+ };
39
+
40
+ //#endregion
41
+ export { configCheck };
42
+ //# sourceMappingURL=config.check.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.check.mjs","names":[],"sources":["../../../../../../../../../../@warlock.js/core/src/cli/commands/doctor/checks/config.check.ts"],"sourcesContent":["import { config } from \"../../../../config/config-getter\";\nimport type { DoctorCheck } from \"../check.types\";\n\n/**\n * Config sections that a typical Warlock HTTP application is expected to\n * register. A missing section here is release/runtime-blocking, so absence is\n * a `fail` rather than a warning.\n */\nconst REQUIRED_CONFIG_SECTIONS = [\"app\", \"http\"] as const;\n\n/**\n * `@mongez/config` returns `null` (not `undefined`) for an unregistered key,\n * so treat both nullish values as \"missing\".\n */\nfunction isPresent(value: unknown): boolean {\n return value !== null && value !== undefined;\n}\n\n/**\n * Verifies that the required top-level config sections are present. Reads each\n * section via `config.get` only — never mutates configuration.\n */\nexport const configCheck: DoctorCheck = {\n name: \"config\",\n run: () => {\n const missing = REQUIRED_CONFIG_SECTIONS.filter((section) => {\n return !isPresent(config.get(section));\n });\n\n if (missing.length > 0) {\n return {\n name: \"config\",\n status: \"fail\",\n detail: `missing required config section(s): ${missing.join(\", \")}`,\n };\n }\n\n return {\n name: \"config\",\n status: \"ok\",\n detail: `required sections present (${REQUIRED_CONFIG_SECTIONS.join(\", \")})`,\n };\n },\n};\n"],"mappings":";;;;;;;;AAQA,MAAM,2BAA2B,CAAC,OAAO,MAAM;;;;;AAM/C,SAAS,UAAU,OAAyB;CAC1C,OAAO,UAAU,QAAQ,UAAU;AACrC;;;;;AAMA,MAAa,cAA2B;CACtC,MAAM;CACN,WAAW;EACT,MAAM,UAAU,yBAAyB,QAAQ,YAAY;GAC3D,OAAO,CAAC,UAAU,OAAO,IAAI,OAAO,CAAC;EACvC,CAAC;EAED,IAAI,QAAQ,SAAS,GACnB,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,uCAAuC,QAAQ,KAAK,IAAI;EAClE;EAGF,OAAO;GACL,MAAM;GACN,QAAQ;GACR,QAAQ,8BAA8B,yBAAyB,KAAK,IAAI,EAAE;EAC5E;CACF;AACF"}
@@ -0,0 +1,28 @@
1
+ import { connectorsManager } from "../../../../connectors/connectors-manager.mjs";
2
+
3
+ //#region ../@warlock.js/core/src/cli/commands/doctor/checks/connectors.check.ts
4
+ /**
5
+ * Reports which connectors are registered and which are currently active. This
6
+ * is informational: a registered-but-inactive connector is normal (it simply
7
+ * has no config wired), so the check passes as long as the manager can be
8
+ * enumerated and reports its active set.
9
+ *
10
+ * Read-only: calls `connectorsManager.list()` and each connector's
11
+ * `isActive()` (a pure getter) only.
12
+ */
13
+ const connectorsCheck = {
14
+ name: "connectors",
15
+ run: () => {
16
+ const connectors = connectorsManager.list();
17
+ const activeNames = connectors.filter((connector) => connector.isActive()).map((connector) => connector.name);
18
+ return {
19
+ name: "connectors",
20
+ status: "ok",
21
+ detail: activeNames.length > 0 ? `${connectors.length} registered, active: ${activeNames.join(", ")}` : `${connectors.length} registered, none active`
22
+ };
23
+ }
24
+ };
25
+
26
+ //#endregion
27
+ export { connectorsCheck };
28
+ //# sourceMappingURL=connectors.check.mjs.map