@alexkroman1/aai-cli 6.11.0 → 8.0.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.
- package/README.md +1 -0
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/_resource-commands.d.ts +23 -0
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
- package/dist/_slug-api.d.ts +5 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
- package/dist/_templates.d.ts +3 -4
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
- package/dist/cli.mjs +346 -349
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
- package/dist/eval-DfQ8cm5p.mjs +56 -0
- package/dist/eval.d.ts +43 -0
- package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +442 -80
- package/dist/scaffold/package.json +6 -3
- package/dist/scaffold/server.mjs +11 -4
- package/dist/scaffold/tsconfig.json +0 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
- package/dist/templates/briefing-desk/agent.test.ts +350 -0
- package/dist/templates/briefing-desk/agent.ts +30 -0
- package/dist/templates/briefing-desk/shared.ts +200 -0
- package/dist/templates/briefing-desk/system-prompt.md +39 -0
- package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
- package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
- package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
- package/dist/templates/call-audit/agent.eval.test.ts +474 -0
- package/dist/templates/call-audit/agent.test.ts +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
- package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
- package/dist/templates/infocom-adventure/agent.test.ts +53 -24
- package/dist/templates/infocom-adventure/agent.ts +21 -0
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/infocom-adventure/shared.ts +20 -9
- package/dist/templates/infocom-adventure/system-prompt.md +1 -1
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
- package/dist/templates/link-digest/agent.eval.test.ts +195 -0
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
- package/dist/templates/night-owl/agent.eval.test.ts +272 -0
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
- package/dist/templates/personal-finance/system-prompt.md +4 -0
- package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
- package/dist/templates/pipeline-simple/agent.ts +2 -2
- package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
- package/dist/templates/pizza-ordering/agent.test.ts +42 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/pizza-ordering/shared.ts +20 -1
- package/dist/templates/pizza-ordering/system-prompt.md +6 -0
- package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
- package/dist/templates/podcast-digest/agent.test.ts +717 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
- package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
- package/dist/templates/recap-workflow/agent.test.ts +97 -94
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
- package/dist/templates/redline/agent.eval.test.ts +209 -0
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/address.ts +87 -1
- package/dist/templates/retail/agent.eval.test.ts +314 -0
- package/dist/templates/retail/agent.test.ts +195 -112
- package/dist/templates/retail/agent.ts +35 -1
- package/dist/templates/retail/cancel.ts +116 -0
- package/dist/templates/retail/client.tsx +265 -117
- package/dist/templates/retail/payment.ts +107 -0
- package/dist/templates/retail/pending.test.ts +196 -0
- package/dist/templates/retail/pending.ts +137 -0
- package/dist/templates/retail/registry.test.ts +150 -7
- package/dist/templates/retail/returns.ts +102 -0
- package/dist/templates/retail/shared.test.ts +42 -0
- package/dist/templates/retail/shared.ts +35 -0
- package/dist/templates/retail/store.test.ts +84 -15
- package/dist/templates/retail/store.ts +231 -48
- package/dist/templates/retail/swap.test.ts +4 -4
- package/dist/templates/retail/swap.ts +248 -15
- package/dist/templates/retail/system-prompt.md +69 -28
- package/dist/templates/retail/tools/cancel_change.ts +33 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
- package/dist/templates/retail/tools/confirm_change.ts +42 -0
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
- package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
- package/dist/templates/retail/tools/modify_user_address.ts +13 -16
- package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
- package/dist/templates/simple/agent.eval.test.ts +54 -0
- package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
- package/dist/templates/spoken-summary/agent.test.ts +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- package/dist/templates/support-line/agent.eval.test.ts +273 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/system-prompt.md +2 -1
- package/dist/templates/support-line/tools/answer_question.ts +66 -4
- package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
- package/dist/templates/transcription-workflow/agent.test.ts +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
- package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
- package/dist/templates/travel-concierge/agent.test.ts +174 -43
- package/dist/templates/travel-concierge/agent.ts +18 -9
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +180 -12
- package/dist/templates/travel-concierge/system-prompt.md +7 -1
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
- package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
- package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
- package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
- package/dist/templates/web-researcher/system-prompt.md +15 -4
- package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
- package/dist/test.d.ts +29 -5
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/eject-31gjtaHF.mjs +0 -116
- package/dist/eject.d.ts +0 -60
- package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
|
@@ -27,16 +27,21 @@
|
|
|
27
27
|
* runs a real one.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
import type {
|
|
30
|
+
import type { WorkflowClient } from "@alexkroman1/aai";
|
|
31
31
|
import {
|
|
32
|
-
createProgressStream,
|
|
33
32
|
createRunSnapshot,
|
|
34
|
-
createStubWorkflows,
|
|
35
33
|
createToolContext,
|
|
36
|
-
|
|
34
|
+
parseSchemaInput,
|
|
35
|
+
schemaInputIssues,
|
|
36
|
+
toolRunner,
|
|
37
37
|
withDiscoveredTools,
|
|
38
38
|
} from "@alexkroman1/aai/testing";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
installStubStepFetch,
|
|
41
|
+
mockWorkflows,
|
|
42
|
+
installStubGateway as stubGateway,
|
|
43
|
+
} from "@alexkroman1/aai/testing/vitest";
|
|
44
|
+
import type { WorkflowRunSnapshot } from "@alexkroman1/aai/workflow-api";
|
|
40
45
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
41
46
|
import { createHook, type Hook, sleep } from "workflow";
|
|
42
47
|
import authoredAgent from "./agent.ts";
|
|
@@ -80,24 +85,27 @@ const agentDef = withDiscoveredTools(
|
|
|
80
85
|
import.meta.glob("./tools/*.ts", { eager: true }),
|
|
81
86
|
);
|
|
82
87
|
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Every tool here is driven through the agent's own table, by the name the model
|
|
90
|
+
* calls.
|
|
91
|
+
*
|
|
92
|
+
* The second parameter is args-or-context, which is `runTool`'s own shape: four
|
|
93
|
+
* of this desk's five tools take no arguments, and the `{}` those calls were
|
|
94
|
+
* obliged to pass sat between the two values a reader cares about.
|
|
95
|
+
*/
|
|
96
|
+
const run = toolRunner(agentDef);
|
|
86
97
|
|
|
87
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* A `ctx.workflows` that records `start` and answers the lookups from a fixture.
|
|
100
|
+
*
|
|
101
|
+
* `mockWorkflows` (`@alexkroman1/aai/testing/vitest`) is the whole thing — a
|
|
102
|
+
* `vi.fn` per method over one `runs` list, with `stream`/`streamTail` left
|
|
103
|
+
* rejecting because `recap_progress` reads progress through `lastLine` and
|
|
104
|
+
* composing those two by hand is the hazard `lastLine` exists to remove. What
|
|
105
|
+
* is local is only which workflow this desk declares.
|
|
106
|
+
*/
|
|
88
107
|
function stubWorkflows(runs: WorkflowRunSnapshot[] = []): WorkflowClient {
|
|
89
|
-
return
|
|
90
|
-
start: vi.fn(async () => "wrun_stub"),
|
|
91
|
-
get: vi.fn(async () => runs[0]),
|
|
92
|
-
find: vi.fn(async () => runs),
|
|
93
|
-
recent: vi.fn(async () => runs),
|
|
94
|
-
cancel: vi.fn(async () => true),
|
|
95
|
-
wakeUp: vi.fn(async () => 0),
|
|
96
|
-
// A tail of 0 means "one line written", which is the case the tools read.
|
|
97
|
-
streamTail: vi.fn(async () => 0),
|
|
98
|
-
stream: vi.fn(async () => createProgressStream([])),
|
|
99
|
-
listing: () => [{ name: "recap" }],
|
|
100
|
-
});
|
|
108
|
+
return mockWorkflows({ runs, names: ["recap"] });
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/** A finished recap, as the workflow's output reaches the tools. */
|
|
@@ -122,13 +130,19 @@ describe("the agent declares its workflow", () => {
|
|
|
122
130
|
});
|
|
123
131
|
|
|
124
132
|
test("with an input schema, so a bad URL fails at the call site", async () => {
|
|
125
|
-
|
|
133
|
+
// `parseSchemaInput` / `schemaInputIssues` rather than a reach through
|
|
134
|
+
// `["~standard"].validate`: that is the vendor WIRE contract, and whether it
|
|
135
|
+
// answers synchronously or with a promise is the vendor's business — a
|
|
136
|
+
// missing `await` there leaves `.issues` undefined and the refusing half
|
|
137
|
+
// passes for the wrong reason.
|
|
138
|
+
const parsed = await parseSchemaInput(recap.input, {
|
|
126
139
|
url: "https://example.com/a.mp3",
|
|
127
140
|
requestedBy: "s",
|
|
128
141
|
});
|
|
129
|
-
expect(
|
|
130
|
-
|
|
131
|
-
|
|
142
|
+
expect(parsed).toMatchObject({ url: "https://example.com/a.mp3" });
|
|
143
|
+
expect(
|
|
144
|
+
await schemaInputIssues(recap.input, { url: "not a url", requestedBy: "s" }),
|
|
145
|
+
).toBeDefined();
|
|
132
146
|
});
|
|
133
147
|
|
|
134
148
|
test("and names the credential its steps read, so a deploy checks for it", () => {
|
|
@@ -156,7 +170,7 @@ describe("request_recap", () => {
|
|
|
156
170
|
test("starts a run keyed by the session, so a later turn can find it", async () => {
|
|
157
171
|
const workflows = stubWorkflows();
|
|
158
172
|
const ctx = createToolContext({ workflows });
|
|
159
|
-
const result = await run("request_recap",
|
|
173
|
+
const result = await run("request_recap", ctx);
|
|
160
174
|
|
|
161
175
|
expect(workflows.start).toHaveBeenCalledWith(
|
|
162
176
|
recap,
|
|
@@ -170,7 +184,7 @@ describe("request_recap", () => {
|
|
|
170
184
|
|
|
171
185
|
test("passes the definition rather than its name", async () => {
|
|
172
186
|
const workflows = stubWorkflows();
|
|
173
|
-
await run("request_recap",
|
|
187
|
+
await run("request_recap", createToolContext({ workflows }));
|
|
174
188
|
// The def overload is what types the input and turns a rename into a compile
|
|
175
189
|
// error; a string would still work at runtime and lose both.
|
|
176
190
|
expect(vi.mocked(workflows.start).mock.calls[0]?.[0]).toBe(recap);
|
|
@@ -193,7 +207,7 @@ describe("request_recap", () => {
|
|
|
193
207
|
// failure it prevents is not tidiness: a caller who asks twice would
|
|
194
208
|
// otherwise pay for the same recording being transcribed twice.
|
|
195
209
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
196
|
-
const result = await run("request_recap",
|
|
210
|
+
const result = await run("request_recap", createToolContext({ workflows }));
|
|
197
211
|
expect(result).toMatchObject({ started: false, runId: "wrun_1" });
|
|
198
212
|
expect(workflows.start).not.toHaveBeenCalled();
|
|
199
213
|
});
|
|
@@ -202,7 +216,7 @@ describe("request_recap", () => {
|
|
|
202
216
|
const workflows = stubWorkflows([
|
|
203
217
|
createRunSnapshot({ workflow: "recap", status: "completed", output: finishedOutput() }),
|
|
204
218
|
]);
|
|
205
|
-
const result = await run("request_recap",
|
|
219
|
+
const result = await run("request_recap", createToolContext({ workflows }));
|
|
206
220
|
expect(result).toMatchObject({ started: true });
|
|
207
221
|
expect(workflows.start).toHaveBeenCalledTimes(1);
|
|
208
222
|
});
|
|
@@ -211,7 +225,7 @@ describe("request_recap", () => {
|
|
|
211
225
|
describe("recap_status", () => {
|
|
212
226
|
test("says nothing was started when the key has no runs", async () => {
|
|
213
227
|
const ctx = createToolContext({ workflows: stubWorkflows([]) });
|
|
214
|
-
const result = await run("recap_status",
|
|
228
|
+
const result = await run("recap_status", ctx);
|
|
215
229
|
expect(result).toMatchObject({ runs: [], note: "Nothing started yet." });
|
|
216
230
|
});
|
|
217
231
|
|
|
@@ -221,7 +235,7 @@ describe("recap_status", () => {
|
|
|
221
235
|
const workflows = stubWorkflows([
|
|
222
236
|
createRunSnapshot({ workflow: "recap", status: "completed", output: finishedOutput() }),
|
|
223
237
|
]);
|
|
224
|
-
const result = (await run("recap_status",
|
|
238
|
+
const result = (await run("recap_status", createToolContext({ workflows }))) as {
|
|
225
239
|
runs: string[];
|
|
226
240
|
};
|
|
227
241
|
expect(result.runs[0]).toContain("air quality");
|
|
@@ -239,7 +253,6 @@ describe("recap_status", () => {
|
|
|
239
253
|
];
|
|
240
254
|
const result = (await run(
|
|
241
255
|
"recap_status",
|
|
242
|
-
{},
|
|
243
256
|
createToolContext({ workflows: stubWorkflows(runs) }),
|
|
244
257
|
)) as { runs: string[] };
|
|
245
258
|
expect(result.runs[0]).toContain("transcript deleted");
|
|
@@ -249,7 +262,7 @@ describe("recap_status", () => {
|
|
|
249
262
|
const ctx = createToolContext({
|
|
250
263
|
workflows: stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]),
|
|
251
264
|
});
|
|
252
|
-
const result = (await run("recap_status",
|
|
265
|
+
const result = (await run("recap_status", ctx)) as { runs: string[] };
|
|
253
266
|
expect(result.runs[0]).toContain("Still working");
|
|
254
267
|
});
|
|
255
268
|
|
|
@@ -262,7 +275,6 @@ describe("recap_status", () => {
|
|
|
262
275
|
];
|
|
263
276
|
const result = (await run(
|
|
264
277
|
"recap_status",
|
|
265
|
-
{},
|
|
266
278
|
createToolContext({ workflows: stubWorkflows(runs) }),
|
|
267
279
|
)) as { runs: string[] };
|
|
268
280
|
expect(result.runs[0]).toContain("rolled back");
|
|
@@ -272,7 +284,7 @@ describe("recap_status", () => {
|
|
|
272
284
|
test("bounds how many past runs it reads aloud", async () => {
|
|
273
285
|
const workflows = stubWorkflows([]);
|
|
274
286
|
const ctx = createToolContext({ workflows });
|
|
275
|
-
await run("recap_status",
|
|
287
|
+
await run("recap_status", ctx);
|
|
276
288
|
// A voice reply cannot be a list of twenty runs.
|
|
277
289
|
expect(workflows.find).toHaveBeenCalledWith(recap, ctx.sessionId, { limit: 3 });
|
|
278
290
|
});
|
|
@@ -281,28 +293,30 @@ describe("recap_status", () => {
|
|
|
281
293
|
describe("recap_progress", () => {
|
|
282
294
|
test("reads the run's own progress line rather than its status", async () => {
|
|
283
295
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
284
|
-
vi.mocked(workflows.
|
|
285
|
-
const result = await run("recap_progress",
|
|
296
|
+
vi.mocked(workflows.lastLine).mockResolvedValue("Transcript processing.");
|
|
297
|
+
const result = await run("recap_progress", createToolContext({ workflows }));
|
|
286
298
|
expect(result).toMatchObject({ progress: "Transcript processing." });
|
|
287
299
|
});
|
|
288
300
|
|
|
289
301
|
test("asks for the LAST line, not the whole log", async () => {
|
|
290
302
|
// Every poll narrates, so a twenty-minute run's whole log is eighty lines.
|
|
303
|
+
// `lastLine` is the whole request — the bound that keeps an empty channel
|
|
304
|
+
// from hanging belongs to the method, so nothing here composes
|
|
305
|
+
// `streamTail` and `stream`.
|
|
291
306
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
292
|
-
vi.mocked(workflows.
|
|
293
|
-
await run("recap_progress",
|
|
294
|
-
expect(workflows.
|
|
307
|
+
vi.mocked(workflows.lastLine).mockResolvedValue("a");
|
|
308
|
+
await run("recap_progress", createToolContext({ workflows }));
|
|
309
|
+
expect(workflows.lastLine).toHaveBeenCalledWith("wrun_1");
|
|
295
310
|
});
|
|
296
311
|
|
|
297
|
-
test("a run that has written nothing yet says so
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
312
|
+
test("a run that has written nothing yet says so", async () => {
|
|
313
|
+
// `lastLine` resolves `undefined` for an empty channel, and this is the arm
|
|
314
|
+
// the tool branches on. That an empty channel does not HANG — it is never
|
|
315
|
+
// closed, so a stream opened on one waits for a line that may never come —
|
|
316
|
+
// is `lastLine`'s own guarantee now, and `aai`'s to test.
|
|
301
317
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
302
|
-
|
|
303
|
-
const result = await run("recap_progress", {}, createToolContext({ workflows }));
|
|
318
|
+
const result = await run("recap_progress", createToolContext({ workflows }));
|
|
304
319
|
expect(result).toMatchObject({ note: expect.stringContaining("nothing to report") });
|
|
305
|
-
expect(workflows.stream).not.toHaveBeenCalled();
|
|
306
320
|
});
|
|
307
321
|
});
|
|
308
322
|
|
|
@@ -343,7 +357,7 @@ describe("keep_transcript — the signal", () => {
|
|
|
343
357
|
describe("cancel_recap", () => {
|
|
344
358
|
test("cancels the live run", async () => {
|
|
345
359
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
346
|
-
const result = await run("cancel_recap",
|
|
360
|
+
const result = await run("cancel_recap", createToolContext({ workflows }));
|
|
347
361
|
expect(workflows.cancel).toHaveBeenCalledWith("wrun_1");
|
|
348
362
|
expect(result).toMatchObject({ cancelled: true });
|
|
349
363
|
});
|
|
@@ -354,7 +368,7 @@ describe("cancel_recap", () => {
|
|
|
354
368
|
// stops replaying the run, so the compensations never fire. A template that
|
|
355
369
|
// implied otherwise would be teaching the wrong thing.
|
|
356
370
|
const workflows = stubWorkflows([createRunSnapshot({ workflow: "recap", status: "running" })]);
|
|
357
|
-
const result = (await run("cancel_recap",
|
|
371
|
+
const result = (await run("cancel_recap", createToolContext({ workflows }))) as {
|
|
358
372
|
note: string;
|
|
359
373
|
};
|
|
360
374
|
expect(result.note).toContain("left behind");
|
|
@@ -365,13 +379,13 @@ describe("cancel_recap", () => {
|
|
|
365
379
|
createRunSnapshot({ workflow: "recap", status: "completed", output: finishedOutput() }),
|
|
366
380
|
]);
|
|
367
381
|
vi.mocked(workflows.cancel).mockResolvedValue(false);
|
|
368
|
-
const result = await run("cancel_recap",
|
|
382
|
+
const result = await run("cancel_recap", createToolContext({ workflows }));
|
|
369
383
|
expect(result).toMatchObject({ cancelled: false, note: "That one had already finished." });
|
|
370
384
|
});
|
|
371
385
|
|
|
372
386
|
test("says nothing was started when the key has no runs", async () => {
|
|
373
387
|
const workflows = stubWorkflows([]);
|
|
374
|
-
const result = await run("cancel_recap",
|
|
388
|
+
const result = await run("cancel_recap", createToolContext({ workflows }));
|
|
375
389
|
expect(result).toMatchObject({ cancelled: false, note: "Nothing started yet." });
|
|
376
390
|
expect(workflows.cancel).not.toHaveBeenCalled();
|
|
377
391
|
});
|
|
@@ -379,20 +393,22 @@ describe("cancel_recap", () => {
|
|
|
379
393
|
|
|
380
394
|
// ---- The steps --------------------------------------------------------------
|
|
381
395
|
|
|
382
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* A provider answering `body` with `status`, recording what it was asked.
|
|
398
|
+
*
|
|
399
|
+
* Published into `stepFetch`'s OWN slot, not over `globalThis.fetch`. Every
|
|
400
|
+
* request in this file goes through `stepFetch` — `request()` and
|
|
401
|
+
* `discardTranscript` reach it directly, `stepTranscribeSubmitClassified`
|
|
402
|
+
* through the SDK — and `step-fetch.ts` falls back to `globalThis.fetch` only
|
|
403
|
+
* when nothing is published. A global stub therefore passed while exercising a
|
|
404
|
+
* path production never takes; every sibling template already stubs the slot,
|
|
405
|
+
* and `link-digest/agent.test.ts` states the rule this one used to break.
|
|
406
|
+
*
|
|
407
|
+
* `installStubStepFetch` unpublishes on `onTestFinished`, so there is no restore
|
|
408
|
+
* registry here and a stub cannot reach the next file.
|
|
409
|
+
*/
|
|
383
410
|
function stubProvider(body: unknown, status = 200) {
|
|
384
|
-
|
|
385
|
-
vi.stubGlobal(
|
|
386
|
-
"fetch",
|
|
387
|
-
vi.fn(async (url: string, init: RequestInit = {}) => {
|
|
388
|
-
calls.push({ url, init });
|
|
389
|
-
return new Response(typeof body === "string" ? body : JSON.stringify(body), {
|
|
390
|
-
status,
|
|
391
|
-
headers: { "Content-Type": "application/json" },
|
|
392
|
-
});
|
|
393
|
-
}),
|
|
394
|
-
);
|
|
395
|
-
return calls;
|
|
411
|
+
return installStubStepFetch(() => ({ status, body })).calls;
|
|
396
412
|
}
|
|
397
413
|
|
|
398
414
|
describe("submitRecording", () => {
|
|
@@ -407,10 +423,10 @@ describe("submitRecording", () => {
|
|
|
407
423
|
expect(await submitRecording("https://example.com/a.mp3")).toEqual({ id: "t_1" });
|
|
408
424
|
|
|
409
425
|
const call = calls[0];
|
|
410
|
-
expect(call?.
|
|
426
|
+
expect(call?.method).toBe("POST");
|
|
411
427
|
// `speaker_labels` is this desk's own request, carried through the SDK's
|
|
412
428
|
// `params` passthrough; the model field is the SDK's and is PLURAL.
|
|
413
|
-
expect(JSON.parse(String(call?.
|
|
429
|
+
expect(JSON.parse(String(call?.body))).toMatchObject({
|
|
414
430
|
audio_url: "https://example.com/a.mp3",
|
|
415
431
|
speaker_labels: true,
|
|
416
432
|
});
|
|
@@ -418,8 +434,7 @@ describe("submitRecording", () => {
|
|
|
418
434
|
// OpenAI-compatible LLM gateway `summarize` calls. The SDK spells the
|
|
419
435
|
// header `Authorization`; HTTP header names are case-insensitive, so the
|
|
420
436
|
// lookup is too rather than pinning one casing.
|
|
421
|
-
const
|
|
422
|
-
const auth = Object.entries(headers ?? {}).find(
|
|
437
|
+
const auth = Object.entries(call?.headers ?? {}).find(
|
|
423
438
|
([name]) => name.toLowerCase() === "authorization",
|
|
424
439
|
);
|
|
425
440
|
expect(auth?.[1]).toBe("sk-test");
|
|
@@ -488,7 +503,7 @@ describe("discardTranscript — the compensation", () => {
|
|
|
488
503
|
test("deletes the transcript this run created", async () => {
|
|
489
504
|
const calls = stubProvider({ id: "t_1" });
|
|
490
505
|
await expect(discardTranscript("t_1")).resolves.toBeUndefined();
|
|
491
|
-
expect(calls[0]?.
|
|
506
|
+
expect(calls[0]?.method).toBe("DELETE");
|
|
492
507
|
expect(calls[0]?.url).toContain("/t_1");
|
|
493
508
|
});
|
|
494
509
|
|
|
@@ -580,19 +595,11 @@ describe("awaitTranscript — the polling port", () => {
|
|
|
580
595
|
|
|
581
596
|
/** A provider whose status endpoint answers `statuses` in order. */
|
|
582
597
|
function stubStatuses(statuses: readonly Record<string, unknown>[]) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
call += 1;
|
|
589
|
-
return new Response(JSON.stringify(body), {
|
|
590
|
-
status: 200,
|
|
591
|
-
headers: { "Content-Type": "application/json" },
|
|
592
|
-
});
|
|
593
|
-
}),
|
|
594
|
-
);
|
|
595
|
-
return () => call;
|
|
598
|
+
// Into `stepFetch`'s slot, for the reason `stubProvider` above gives.
|
|
599
|
+
const stub = installStubStepFetch(() => ({
|
|
600
|
+
body: statuses[Math.min(stub.calls.length - 1, statuses.length - 1)],
|
|
601
|
+
}));
|
|
602
|
+
return () => stub.calls.length;
|
|
596
603
|
}
|
|
597
604
|
|
|
598
605
|
test("keeps polling while the job is queued or processing", async () => {
|
|
@@ -665,15 +672,14 @@ describe("askWhetherToKeep — the expense port", () => {
|
|
|
665
672
|
|
|
666
673
|
test("keeps the transcript when the caller says to, and deletes nothing", async () => {
|
|
667
674
|
vi.mocked(createHook).mockReturnValue(hookAnswering({ keep: true }));
|
|
668
|
-
const
|
|
669
|
-
vi.stubGlobal("fetch", fetchSpy);
|
|
675
|
+
const provider = installStubStepFetch();
|
|
670
676
|
|
|
671
677
|
const compensations = [{ label: "transcript t_1", undo: async () => undefined }];
|
|
672
678
|
expect(await askWhetherToKeep("s_1", "t_1", compensations)).toEqual({
|
|
673
679
|
kept: true,
|
|
674
680
|
answered: true,
|
|
675
681
|
});
|
|
676
|
-
expect(
|
|
682
|
+
expect(provider.calls).toEqual([]);
|
|
677
683
|
// The undo stays on the stack: a later failure still has something to reverse.
|
|
678
684
|
expect(compensations).toHaveLength(1);
|
|
679
685
|
});
|
|
@@ -687,7 +693,7 @@ describe("askWhetherToKeep — the expense port", () => {
|
|
|
687
693
|
kept: false,
|
|
688
694
|
answered: true,
|
|
689
695
|
});
|
|
690
|
-
expect(calls[0]?.
|
|
696
|
+
expect(calls[0]?.method).toBe("DELETE");
|
|
691
697
|
// Leaving it would be harmless — the undo tolerates a 404 — and would still
|
|
692
698
|
// narrate an unwind that reverses something already gone.
|
|
693
699
|
expect(compensations).toHaveLength(0);
|
|
@@ -700,7 +706,7 @@ describe("askWhetherToKeep — the expense port", () => {
|
|
|
700
706
|
const calls = stubProvider({ id: "t_1" });
|
|
701
707
|
|
|
702
708
|
expect(await askWhetherToKeep("s_1", "t_1", [])).toEqual({ kept: false, answered: false });
|
|
703
|
-
expect(calls[0]?.
|
|
709
|
+
expect(calls[0]?.method).toBe("DELETE");
|
|
704
710
|
});
|
|
705
711
|
|
|
706
712
|
test("claims the token BEFORE the caller is asked to answer it", async () => {
|
|
@@ -711,13 +717,10 @@ describe("askWhetherToKeep — the expense port", () => {
|
|
|
711
717
|
vi.mocked(createHook).mockReturnValue(
|
|
712
718
|
hookAnswering({ keep: true }, () => order.push("claimed")),
|
|
713
719
|
);
|
|
714
|
-
|
|
715
|
-
"
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
return new Response("{}", { status: 200 });
|
|
719
|
-
}),
|
|
720
|
-
);
|
|
720
|
+
installStubStepFetch(() => {
|
|
721
|
+
order.push("asked");
|
|
722
|
+
return { body: {} };
|
|
723
|
+
});
|
|
721
724
|
|
|
722
725
|
await askWhetherToKeep("s_1", "t_1", []);
|
|
723
726
|
expect(order[0]).toBe("claimed");
|
|
@@ -11,17 +11,14 @@ export default tool({
|
|
|
11
11
|
// otherwise nothing to say — and this run has real news in between,
|
|
12
12
|
// since every poll narrates.
|
|
13
13
|
//
|
|
14
|
-
// `
|
|
15
|
-
// never closed — no step knows it is the last one — so
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const stream = await ctx.workflows.stream(latest.runId, { startIndex: -1 });
|
|
24
|
-
for await (const line of stream) return { progress: String(line) };
|
|
25
|
-
return { note: "Submitted, nothing to report yet." };
|
|
14
|
+
// `lastLine` rather than `streamTail` + `stream` composed here: a progress
|
|
15
|
+
// channel is never closed — no step knows it is the last one — so a stream
|
|
16
|
+
// opened on a run that has written nothing waits forever, which down a phone
|
|
17
|
+
// is a turn that stops with no error and nothing in a log. The bound that
|
|
18
|
+
// prevents it belongs to the method now, and `undefined` is "nothing yet".
|
|
19
|
+
const line = await ctx.workflows.lastLine(latest.runId);
|
|
20
|
+
return line === undefined
|
|
21
|
+
? { note: "Submitted, nothing to report yet." }
|
|
22
|
+
: { progress: String(line) };
|
|
26
23
|
},
|
|
27
24
|
});
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
tool,
|
|
4
|
-
type WorkflowOutputOf,
|
|
5
|
-
type WorkflowRunSnapshot,
|
|
6
|
-
} from "@alexkroman1/aai";
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { isTerminal, type WorkflowRunOf } from "@alexkroman1/aai/workflow-api";
|
|
7
3
|
import { recap } from "../shared.ts";
|
|
8
4
|
|
|
9
5
|
/** How many past runs the status tool will look at. Newest first. */
|
|
@@ -14,10 +10,13 @@ const RECENT_RUNS = 3;
|
|
|
14
10
|
*
|
|
15
11
|
* This is the QUERY, and `isTerminal` is what makes it typed: it narrows to the
|
|
16
12
|
* three finished statuses, which is what puts `run.output` and `run.error`
|
|
17
|
-
* within reach without a cast. `
|
|
18
|
-
*
|
|
13
|
+
* within reach without a cast. `WorkflowRunOf` is the snapshot with that output
|
|
14
|
+
* already named from the declaration — the
|
|
15
|
+
* `WorkflowRunSnapshot<WorkflowOutputOf<typeof recap>>` this file used to
|
|
16
|
+
* compose by hand, for a three-name import — so the signature never reaches past
|
|
17
|
+
* the declaration into the body, and it is still the discriminated union.
|
|
19
18
|
*/
|
|
20
|
-
function describeRun(run:
|
|
19
|
+
function describeRun(run: WorkflowRunOf<typeof recap>): string {
|
|
21
20
|
if (!isTerminal(run)) return "Still working on that one.";
|
|
22
21
|
switch (run.status) {
|
|
23
22
|
case "completed": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* Everything the desk claims to do. `submitRecording`, `checkTranscript` and
|
|
56
56
|
* `discardTranscript` are AssemblyAI's pre-recorded API (`POST`, `GET` and
|
|
57
57
|
* `DELETE` on `/v2/transcript`), and `summarize` is a real model call through
|
|
58
|
-
* `
|
|
58
|
+
* `stepGenerateJsonClassified`. The BATCH API is what makes the polling port honest: it
|
|
59
59
|
* answers with a job id in milliseconds and finishes minutes later, so the wait
|
|
60
60
|
* is the provider's, not a `setTimeout` this template chose. (Its sibling
|
|
61
61
|
* `transcription-workflow` takes the other endpoint — the sync one, which answers in
|
|
@@ -67,17 +67,14 @@
|
|
|
67
67
|
* just your shell.
|
|
68
68
|
*/
|
|
69
69
|
|
|
70
|
-
import {
|
|
70
|
+
import { report, requireStepEnv, stepFetch } from "@alexkroman1/aai/step";
|
|
71
71
|
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
stepGenerateJson,
|
|
79
|
-
stepTranscribeSubmit,
|
|
80
|
-
} from "@alexkroman1/aai/utils";
|
|
72
|
+
stepFetchOk,
|
|
73
|
+
stepGenerateJsonClassified,
|
|
74
|
+
stepTranscribeSubmitClassified,
|
|
75
|
+
toStepError,
|
|
76
|
+
} from "@alexkroman1/aai/step-errors";
|
|
77
|
+
import { errorMessage, isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
81
78
|
import { createHook, FatalError, sleep } from "workflow";
|
|
82
79
|
import { z } from "zod";
|
|
83
80
|
import { retentionToken } from "./tokens.ts";
|
|
@@ -151,7 +148,7 @@ const POINTS = 3;
|
|
|
151
148
|
/**
|
|
152
149
|
* The shape the model must answer in.
|
|
153
150
|
*
|
|
154
|
-
* `
|
|
151
|
+
* `stepGenerateJsonClassified` validates against this and throws PLAINLY when the reply
|
|
155
152
|
* misses, which is the retry policy in one distinction: a model that answered in
|
|
156
153
|
* prose may answer correctly next time, where a 401 will not. `spoken` is the
|
|
157
154
|
* field this template exists for — without it the announced turn has nothing to
|
|
@@ -257,7 +254,7 @@ export async function recapFlow(input: { url: string; requestedBy: string }) {
|
|
|
257
254
|
* to completion in a worker, where the body is what may suspend. Splitting it
|
|
258
255
|
* out keeps `recapFlow` readable and costs nothing, since the WDK transform
|
|
259
256
|
* rewrites a step's DECLARATION rather than its call sites, so a step called
|
|
260
|
-
* from a helper is still a real step (`
|
|
257
|
+
* from a helper is still a real step (`mapConcurrent` rests on the same
|
|
261
258
|
* property).
|
|
262
259
|
*
|
|
263
260
|
* The loop is deterministic despite looking like it is not: every branch turns
|
|
@@ -359,13 +356,14 @@ export async function submitRecording(url: string): Promise<{ id: string }> {
|
|
|
359
356
|
|
|
360
357
|
await report(`Submitting ${new URL(url).hostname} for transcription…`);
|
|
361
358
|
|
|
362
|
-
// `
|
|
363
|
-
// `speech_models` field and the failure classification
|
|
364
|
-
//
|
|
359
|
+
// `stepTranscribeSubmitClassified` owns the endpoint, the raw-key auth, the
|
|
360
|
+
// PLURAL `speech_models` field and the failure classification — the
|
|
361
|
+
// `Classified` suffix being that last part: it is `stepTranscribeSubmit` with
|
|
362
|
+
// `throwStepError` already applied, so a provider refusal stays terminal and a
|
|
363
|
+
// rate limit waits out the delay the provider itself named. `speaker_labels`
|
|
364
|
+
// is this desk's own request, which is what `params` is for — the async API's
|
|
365
365
|
// surface is large and the SDK deliberately does not mirror it.
|
|
366
|
-
return await
|
|
367
|
-
throwStepError,
|
|
368
|
-
);
|
|
366
|
+
return await stepTranscribeSubmitClassified(url, { params: { speaker_labels: true } });
|
|
369
367
|
}
|
|
370
368
|
|
|
371
369
|
/**
|
|
@@ -467,19 +465,20 @@ export async function summarize(url: string, transcript: TranscriptState): Promi
|
|
|
467
465
|
throw new FatalError("That recording came back with no speech in it.");
|
|
468
466
|
}
|
|
469
467
|
|
|
470
|
-
// `
|
|
471
|
-
// it is told not to, parses it, and validates it — all four
|
|
472
|
-
// used to re-derive. `
|
|
473
|
-
// (a bad key, a rejected request) stop rather than
|
|
474
|
-
// attempts, where a reply that missed the SHAPE throws
|
|
475
|
-
|
|
468
|
+
// `stepGenerateJsonClassified` unwraps the fence a model puts around JSON
|
|
469
|
+
// however firmly it is told not to, parses it, and validates it — all four
|
|
470
|
+
// things this step used to re-derive. The `Classified` half is what makes a
|
|
471
|
+
// terminal gateway failure (a bad key, a rejected request) stop rather than
|
|
472
|
+
// burn the remaining attempts, where a reply that missed the SHAPE throws
|
|
473
|
+
// plainly and retries.
|
|
474
|
+
const parsed = await stepGenerateJsonClassified(text, {
|
|
476
475
|
schema: RecapReply,
|
|
477
476
|
system:
|
|
478
477
|
"You write up recordings for someone who will hear the result on a phone call. " +
|
|
479
478
|
`Reply with JSON only: {"headline": string, "points": string[], "spoken": string}. ` +
|
|
480
479
|
`Give exactly ${POINTS} points. "spoken" is ONE sentence, under 30 words, ` +
|
|
481
480
|
"written to be read aloud. No markdown fence, no preamble.",
|
|
482
|
-
})
|
|
481
|
+
});
|
|
483
482
|
|
|
484
483
|
return {
|
|
485
484
|
url,
|
|
@@ -519,27 +518,24 @@ async function request(
|
|
|
519
518
|
url: string,
|
|
520
519
|
init: { method?: string; body?: string } = {},
|
|
521
520
|
): Promise<Response> {
|
|
522
|
-
// `stepFetch`, not `fetch`: it pins HTTP/1.1, so several concurrent
|
|
523
|
-
// this workflow POLLS, so one run is many requests) get a socket
|
|
524
|
-
// than N streams on one connection — and a connection failure
|
|
525
|
-
// `StepTransportError` naming its cause instead of a bare
|
|
521
|
+
// Through `stepFetch`, not `fetch`: it pins HTTP/1.1, so several concurrent
|
|
522
|
+
// runs (and this workflow POLLS, so one run is many requests) get a socket
|
|
523
|
+
// each rather than N streams on one connection — and a connection failure
|
|
524
|
+
// arrives as a `StepTransportError` naming its cause instead of a bare
|
|
526
525
|
// `TypeError: fetch failed`, which for a template whose whole subject is
|
|
527
526
|
// durability is the difference between a diagnosable resume and a mystery.
|
|
528
527
|
// `sdk/step-fetch.ts` carries the measurements.
|
|
529
|
-
|
|
528
|
+
// `stepFetchOk` makes the three-way retry decision: a 401 or a 400 answers the
|
|
529
|
+
// same way on the fourth attempt and burns the step, a 429 or a 5xx is what
|
|
530
|
+
// retries are for, and a `Retry-After` the provider named is waited out rather
|
|
531
|
+
// than replaced by the DevKit's one-second default — which matters here more
|
|
532
|
+
// than usual, because a fan-out of segments hits a rate limit together. The
|
|
533
|
+
// DELETE below stays on plain `stepFetch`, because there a 404 is a SUCCESS.
|
|
534
|
+
return await stepFetchOk(url, {
|
|
530
535
|
...init,
|
|
531
536
|
headers: { authorization: requireStepEnv(API_KEY_ENV), "content-type": "application/json" },
|
|
532
537
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
533
538
|
});
|
|
534
|
-
// The three-way retry decision, made by the SDK: a 401 or a 400 answers the
|
|
535
|
-
// same way on the fourth attempt and burns the step, a 429 or a 5xx is what
|
|
536
|
-
// retries are for, and a `Retry-After` the provider named is waited out rather
|
|
537
|
-
// than replaced by the DevKit's one-second default — which matters here more
|
|
538
|
-
// than usual, because a fan-out of segments hits a rate limit together.
|
|
539
|
-
if (!response.ok) {
|
|
540
|
-
throw toStepError(response, `${init.method ?? "GET"} ${url} failed: HTTP ${response.status}`);
|
|
541
|
-
}
|
|
542
|
-
return response;
|
|
543
539
|
}
|
|
544
540
|
|
|
545
541
|
/** A string field of a JSON body, when it really is one. */
|