|
1 | | -import type { RunState } from "../types/run"; |
2 | | -import { addArtifact } from "./state"; |
3 | | - |
4 | 1 | /** |
5 | | - * Apply a successful step receipt to the run state by appending an artifact |
6 | | - * of the appropriate type. |
7 | | - * |
8 | | - * This is the canonical mapping from verb → artifact type. |
9 | | - * Only applies if receipt.status === "success". |
10 | | - * Never overwrites or removes existing artifacts (append-only). |
| 2 | + * CommandLayer Router — Apply receipt to state |
| 3 | + * Extracts output from a verb receipt and appends the appropriate artifact. |
11 | 4 | */ |
12 | | -export function applyReceiptToState( |
13 | | - state: RunState, |
14 | | - verb: string, |
15 | | - receipt: Record<string, unknown> |
16 | | -): void { |
17 | | - if (!receipt || receipt["status"] !== "success") return; |
18 | 5 |
|
19 | | - const r = receipt["result"]; |
20 | | - if (r === undefined || r === null) return; |
| 6 | +import type { CommonsVerb, RunState, VerbReceipt } from "../types/commons.js"; |
| 7 | +import { VERB_ARTIFACT_TYPE } from "./commonsVerbs.js"; |
| 8 | +import { addArtifact } from "./state.js"; |
| 9 | + |
| 10 | +/** |
| 11 | + * Extract the content artifact from a receipt's output. |
| 12 | + * Each verb may return output in a different shape. |
| 13 | + */ |
| 14 | +function extractArtifactData(verb: CommonsVerb, output: unknown): unknown { |
| 15 | + if (!output || typeof output !== "object") return output; |
| 16 | + const out = output as Record<string, unknown>; |
21 | 17 |
|
22 | 18 | switch (verb) { |
23 | 19 | case "fetch": |
24 | | - // fetch may return items[] or a direct result |
25 | | - { |
26 | | - const items = (r as Record<string, unknown>)["items"]; |
27 | | - const data = |
28 | | - Array.isArray(items) && items.length > 0 ? items[0] : r; |
29 | | - addArtifact(state, "fetched", data); |
30 | | - } |
31 | | - break; |
| 20 | + // Return { content, status, headers, url } or raw content |
| 21 | + return out; |
32 | 22 |
|
33 | 23 | case "clean": |
34 | | - addArtifact(state, "cleaned", r); |
35 | | - break; |
| 24 | + // Return { content } or raw string |
| 25 | + if (typeof out["content"] === "string") return { content: out["content"] }; |
| 26 | + return out; |
36 | 27 |
|
37 | 28 | case "summarize": |
38 | | - addArtifact(state, "summary", r); |
39 | | - break; |
| 29 | + // Return { summary } or raw string |
| 30 | + if (typeof out["summary"] === "string") return { summary: out["summary"] }; |
| 31 | + if (typeof out["content"] === "string") return { summary: out["content"] }; |
| 32 | + return out; |
40 | 33 |
|
41 | 34 | case "classify": |
42 | | - addArtifact(state, "classification", r); |
43 | | - break; |
44 | | - |
45 | | - case "analyze": |
46 | | - addArtifact(state, "analysis", r); |
47 | | - break; |
| 35 | + // Return { label, confidence, categories } etc |
| 36 | + return out; |
48 | 37 |
|
49 | 38 | case "parse": |
50 | | - addArtifact(state, "parsed", r); |
51 | | - break; |
| 39 | + // Return structured data |
| 40 | + return out; |
52 | 41 |
|
53 | | - case "convert": |
54 | | - addArtifact(state, "converted", r); |
55 | | - break; |
| 42 | + case "analyze": |
| 43 | + // Return { analysis } or structured data |
| 44 | + return out; |
56 | 45 |
|
57 | 46 | case "describe": |
58 | | - addArtifact(state, "description", r); |
59 | | - break; |
| 47 | + // Return { description } or string |
| 48 | + return out; |
60 | 49 |
|
61 | 50 | case "explain": |
62 | | - addArtifact(state, "explanation", r); |
63 | | - break; |
| 51 | + // Return { explanation } or string |
| 52 | + return out; |
| 53 | + |
| 54 | + case "convert": |
| 55 | + // Return { content, format } or raw |
| 56 | + return out; |
64 | 57 |
|
65 | 58 | case "format": |
66 | | - // format produces the final artifact |
67 | | - addArtifact(state, "final", r); |
68 | | - break; |
| 59 | + // Return { content, format } or raw |
| 60 | + return out; |
69 | 61 |
|
70 | 62 | default: |
71 | | - // unknown verb: store with verb name as type |
72 | | - addArtifact(state, verb, r); |
73 | | - break; |
| 63 | + return out; |
74 | 64 | } |
75 | 65 | } |
| 66 | + |
| 67 | +/** |
| 68 | + * Apply a successful step receipt to state by adding the appropriate artifact. |
| 69 | + */ |
| 70 | +export function applyReceiptToState( |
| 71 | + verb: CommonsVerb, |
| 72 | + receipt: VerbReceipt, |
| 73 | + state: RunState |
| 74 | +): void { |
| 75 | + if (!receipt.ok) return; // Only apply successful receipts |
| 76 | + |
| 77 | + const artifactType = VERB_ARTIFACT_TYPE[verb]; |
| 78 | + if (!artifactType) return; |
| 79 | + |
| 80 | + const data = extractArtifactData(verb, receipt.output); |
| 81 | + addArtifact(state, artifactType, data); |
| 82 | +} |
| 83 | + |
| 84 | +/** |
| 85 | + * Extract "final" artifact from state after all steps. |
| 86 | + * Returns the most meaningful artifact available. |
| 87 | + */ |
| 88 | +export function extractFinal(state: RunState): unknown { |
| 89 | + // Priority: formatted > converted > summary > analysis > description > explanation > classification > parsed > cleaned > fetched |
| 90 | + const priority = [ |
| 91 | + "formatted", |
| 92 | + "converted", |
| 93 | + "summary", |
| 94 | + "analysis", |
| 95 | + "description", |
| 96 | + "explanation", |
| 97 | + "classification", |
| 98 | + "parsed", |
| 99 | + "cleaned", |
| 100 | + "fetched", |
| 101 | + ]; |
| 102 | + |
| 103 | + for (const type of priority) { |
| 104 | + const artifact = state.artifacts.findLast?.((a) => a.type === type); |
| 105 | + if (artifact) return artifact.data; |
| 106 | + } |
| 107 | + |
| 108 | + // Return last artifact if any |
| 109 | + if (state.artifacts.length > 0) { |
| 110 | + return state.artifacts[state.artifacts.length - 1].data; |
| 111 | + } |
| 112 | + |
| 113 | + return null; |
| 114 | +} |
0 commit comments