@sapiom/harness 0.1.4 → 0.1.5
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/CHANGELOG.md +13 -0
- package/dist/core/canvas-run-state.d.ts +95 -0
- package/dist/core/canvas-run-state.d.ts.map +1 -0
- package/dist/core/canvas-run-state.js +162 -0
- package/dist/core/canvas-run-state.js.map +1 -0
- package/dist/core/canvas-svg.d.ts.map +1 -1
- package/dist/core/canvas-svg.js +28 -8
- package/dist/core/canvas-svg.js.map +1 -1
- package/dist/core/canvas-template.d.ts +4 -3
- package/dist/core/canvas-template.d.ts.map +1 -1
- package/dist/core/canvas-template.js +75 -3
- package/dist/core/canvas-template.js.map +1 -1
- package/dist/core/definition-slug-resolver.d.ts +33 -0
- package/dist/core/definition-slug-resolver.d.ts.map +1 -0
- package/dist/core/definition-slug-resolver.js +81 -0
- package/dist/core/definition-slug-resolver.js.map +1 -0
- package/dist/core/execution-detector.d.ts +59 -0
- package/dist/core/execution-detector.d.ts.map +1 -0
- package/dist/core/execution-detector.js +110 -0
- package/dist/core/execution-detector.js.map +1 -0
- package/dist/core/inject/mcp-config.d.ts.map +1 -1
- package/dist/core/inject/mcp-config.js +9 -1
- package/dist/core/inject/mcp-config.js.map +1 -1
- package/dist/core/render-run-state.d.ts +9 -0
- package/dist/core/render-run-state.d.ts.map +1 -0
- package/dist/core/render-run-state.js +139 -0
- package/dist/core/render-run-state.js.map +1 -0
- package/dist/core/run-spend.d.ts +55 -0
- package/dist/core/run-spend.d.ts.map +1 -0
- package/dist/core/run-spend.js +109 -0
- package/dist/core/run-spend.js.map +1 -0
- package/dist/core/run-state.d.ts +45 -0
- package/dist/core/run-state.d.ts.map +1 -0
- package/dist/core/run-state.js +72 -0
- package/dist/core/run-state.js.map +1 -0
- package/dist/core/run-transactions.d.ts +60 -0
- package/dist/core/run-transactions.d.ts.map +1 -0
- package/dist/core/run-transactions.js +129 -0
- package/dist/core/run-transactions.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +12 -1
- package/dist/core/workflow-registry.d.ts.map +1 -1
- package/dist/core/workflow-registry.js +2 -0
- package/dist/core/workflow-registry.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest.d.ts +5 -0
- package/dist/server/rest.d.ts.map +1 -1
- package/dist/server/rest.js +36 -10
- package/dist/server/rest.js.map +1 -1
- package/dist/server/runs.d.ts +33 -0
- package/dist/server/runs.d.ts.map +1 -0
- package/dist/server/runs.js +124 -0
- package/dist/server/runs.js.map +1 -0
- package/dist/shared/types.d.ts +90 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/web/assets/index-DM84J83Y.js +234 -0
- package/dist/web/assets/{index-DRTrwTku.css → index-DemtWW4L.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BhKGSTTT.js +0 -214
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve-time enrichment: resolves a deployed agent's `definitionSlug` from the
|
|
3
|
+
* Sapiom Agents API by its `definitionId`. The registry only knows the id (from
|
|
4
|
+
* `sapiom.json`'s `{ "definitionId": "188" }`) — the slug lives server-side,
|
|
5
|
+
* keyed by id.
|
|
6
|
+
*
|
|
7
|
+
* Resolution is cached in-memory (id→slug is stable once deployed; a slug
|
|
8
|
+
* never changes for a given id) so repeat calls across requests are free.
|
|
9
|
+
* Null resolutions are NOT cached: a transient network failure should be
|
|
10
|
+
* retryable without a server restart.
|
|
11
|
+
*
|
|
12
|
+
* Mirrors `@sapiom/tools`'s DEFAULT_BASE_URL for the base URL resolution.
|
|
13
|
+
*/
|
|
14
|
+
/** Returns the agents API base URL, honouring the same env-var precedence as
|
|
15
|
+
* @sapiom/tools's DEFAULT_BASE_URL. */
|
|
16
|
+
export function resolveAgentsBaseUrl() {
|
|
17
|
+
return (process.env.SAPIOM_AGENTS_URL ??
|
|
18
|
+
process.env.SAPIOM_TOOLS_BASE ??
|
|
19
|
+
"https://tools.sapiom.ai");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Creates a resolver that fetches `GET /agents/v1/definitions/<id>` with the
|
|
23
|
+
* caller's API key and returns the `slug` field from the response.
|
|
24
|
+
*
|
|
25
|
+
* Safe to call from any request handler: never throws, returns null on any
|
|
26
|
+
* failure (network, 4xx, missing field, unparseable body).
|
|
27
|
+
*/
|
|
28
|
+
export function createDefinitionSlugResolver(opts) {
|
|
29
|
+
const { apiKey, baseUrl = resolveAgentsBaseUrl(), fetchImpl = fetch } = opts;
|
|
30
|
+
const cache = new Map();
|
|
31
|
+
// Each id's resolution failure is logged at most once. resolve() runs on
|
|
32
|
+
// every /api/state and /api/workflows poll and null results are deliberately
|
|
33
|
+
// not cached (so a transient failure stays retryable), so without this a
|
|
34
|
+
// persistent failure — e.g. the harness signed into an account that can't
|
|
35
|
+
// see this agent — would reprint on every poll. One line per id is enough to
|
|
36
|
+
// diagnose why the snippet panel fell back to the project name.
|
|
37
|
+
const loggedFailures = new Set();
|
|
38
|
+
const warnOnce = (definitionId, reason) => {
|
|
39
|
+
if (loggedFailures.has(definitionId))
|
|
40
|
+
return;
|
|
41
|
+
loggedFailures.add(definitionId);
|
|
42
|
+
console.error(`[harness] could not resolve deployed-agent slug for definitionId=${definitionId} ` +
|
|
43
|
+
`at ${baseUrl} (${reason}); the snippet panel will fall back to the project name — ` +
|
|
44
|
+
`check the harness is signed into the account that owns this agent`);
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
async resolve(definitionId) {
|
|
48
|
+
// Not signed in: expected (a harness launched without auth), not a
|
|
49
|
+
// failure worth logging — the panel's project-name fallback covers it.
|
|
50
|
+
if (!apiKey)
|
|
51
|
+
return null;
|
|
52
|
+
const cached = cache.get(definitionId);
|
|
53
|
+
if (cached !== undefined)
|
|
54
|
+
return cached;
|
|
55
|
+
try {
|
|
56
|
+
const url = `${baseUrl}/agents/v1/definitions/${encodeURIComponent(definitionId)}`;
|
|
57
|
+
const response = await fetchImpl(url, {
|
|
58
|
+
headers: { "x-sapiom-api-key": apiKey },
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
warnOnce(definitionId, `HTTP ${response.status}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const body = (await response.json());
|
|
65
|
+
const slug = typeof body.slug === "string" ? body.slug : null;
|
|
66
|
+
if (slug !== null) {
|
|
67
|
+
cache.set(definitionId, slug);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
warnOnce(definitionId, "response had no string `slug` field");
|
|
71
|
+
}
|
|
72
|
+
return slug;
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
warnOnce(definitionId, err instanceof Error ? err.message : String(err));
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=definition-slug-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition-slug-resolver.js","sourceRoot":"","sources":["../../src/core/definition-slug-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;wCACwC;AACxC,MAAM,UAAU,oBAAoB;IAClC,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,OAAO,CAAC,GAAG,CAAC,iBAAiB;QAC7B,yBAAyB,CAC1B,CAAC;AACJ,CAAC;AAMD;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAK5C;IACC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,oBAAoB,EAAE,EAAE,SAAS,GAAG,KAAK,EAAE,GAAG,IAAI,CAAC;IAC7E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,yEAAyE;IACzE,6EAA6E;IAC7E,yEAAyE;IACzE,0EAA0E;IAC1E,6EAA6E;IAC7E,gEAAgE;IAChE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,YAAoB,EAAE,MAAc,EAAQ,EAAE;QAC9D,IAAI,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC;YAAE,OAAO;QAC7C,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACjC,OAAO,CAAC,KAAK,CACX,oEAAoE,YAAY,GAAG;YACjF,MAAM,OAAO,KAAK,MAAM,4DAA4D;YACpF,mEAAmE,CACtE,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,YAAoB;YAChC,mEAAmE;YACnE,uEAAuE;YACvE,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAEzB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACvC,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YAExC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,GAAG,OAAO,0BAA0B,kBAAkB,CAAC,YAAY,CAAC,EAAE,CAAC;gBACnF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;oBACpC,OAAO,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE;iBACxC,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBACjB,QAAQ,CAAC,YAAY,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;oBAClD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAC;gBAChE,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC9D,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,KAAK,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAChC,CAAC;qBAAM,CAAC;oBACN,QAAQ,CAAC,YAAY,EAAE,qCAAqC,CAAC,CAAC;gBAChE,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,QAAQ,CACN,YAAY,EACZ,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution detection — mirrors {@link PortDetector} (core/port-detector.ts).
|
|
3
|
+
*
|
|
4
|
+
* A run can start two ways in the harness, and this catches both:
|
|
5
|
+
* 1. The CLI prints `✓ Started execution <executionId>` (`sapiom agents run`).
|
|
6
|
+
* 2. The coding agent starts the run via the MCP run tool — the common path —
|
|
7
|
+
* whose result carries the id as an `executionId` field
|
|
8
|
+
* (`"executionId":"<id>"` / `executionId: <id>`).
|
|
9
|
+
* The detector is fed the SAME tool.call output PortDetector is (server/index.ts
|
|
10
|
+
* `onNormalizedEvent`, including the tool RESPONSE summary), so an MCP-triggered
|
|
11
|
+
* run is caught without changing how runs are triggered. It fires `onExecution`
|
|
12
|
+
* once per distinct (session, executionId); the integrator wires that to
|
|
13
|
+
* broadcast `{ type: "execution.started", … }` on /ws/events.
|
|
14
|
+
*
|
|
15
|
+
* Same streaming-safe design as PortDetector: a match touching the very end of
|
|
16
|
+
* the buffered text is held back (the id may still be growing in the next
|
|
17
|
+
* chunk) and finalized by `flush()`, which the integrator calls right after
|
|
18
|
+
* feeding a discrete, already-complete string (one tool.call field).
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Pure parser: every execution id announced in a chunk of CLI/tool output, in
|
|
22
|
+
* order (empty when the chunk announces none). This is the mutation-tested
|
|
23
|
+
* core — the {@link ExecutionDetector} class only adds streaming buffering and
|
|
24
|
+
* per-session dedupe on top of it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseExecutionIds(text: string): string[];
|
|
27
|
+
/** Where a detected run executes. Only `"prod"` is emitted today: the CLI's
|
|
28
|
+
* `Started execution` line is the prod-run announcement; local runs are
|
|
29
|
+
* rendered from their final result, not polled (see the design). */
|
|
30
|
+
export type ExecutionTarget = "prod" | "local";
|
|
31
|
+
export interface ExecutionDetectorDeps {
|
|
32
|
+
onExecution(harnessSessionId: string, executionId: string, target: ExecutionTarget): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Stateful, streaming-safe, and dedupes per (session, executionId) so a run id
|
|
36
|
+
* that reappears in output only ever fires `onExecution` once for the session's
|
|
37
|
+
* lifetime. `reset()` clears a session's state on exit so a reused session id
|
|
38
|
+
* starts clean.
|
|
39
|
+
*/
|
|
40
|
+
export declare class ExecutionDetector {
|
|
41
|
+
private readonly deps;
|
|
42
|
+
private readonly buffers;
|
|
43
|
+
private readonly seen;
|
|
44
|
+
constructor(deps: ExecutionDetectorDeps);
|
|
45
|
+
feed(chunk: string, harnessSessionId: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Finalizes whatever's held back for a session, immediately. Call this right
|
|
48
|
+
* after `feed()` whenever the fed text is a discrete, complete string (one
|
|
49
|
+
* tool.call field): without it, an id landing at the very end — the common
|
|
50
|
+
* shape "✓ Started execution exec_123" — would be held pending forever, since
|
|
51
|
+
* no "next chunk" is coming.
|
|
52
|
+
*/
|
|
53
|
+
flush(harnessSessionId: string): void;
|
|
54
|
+
/** Drops buffered/dedupe state for a session — call on session exit so a
|
|
55
|
+
* reused session id starts clean. */
|
|
56
|
+
reset(harnessSessionId: string): void;
|
|
57
|
+
private tryEmit;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=execution-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-detector.d.ts","sourceRoot":"","sources":["../../src/core/execution-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAgBH;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMxD;AAED;;qEAEqE;AACrE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,WAAW,CACT,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,GACtB,IAAI,CAAC;CACT;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAIhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkC;gBAE1B,IAAI,EAAE,qBAAqB;IAExD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAsBnD;;;;;;OAMG;IACH,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAUrC;0CACsC;IACtC,KAAK,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAKrC,OAAO,CAAC,OAAO;CAUhB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution detection — mirrors {@link PortDetector} (core/port-detector.ts).
|
|
3
|
+
*
|
|
4
|
+
* A run can start two ways in the harness, and this catches both:
|
|
5
|
+
* 1. The CLI prints `✓ Started execution <executionId>` (`sapiom agents run`).
|
|
6
|
+
* 2. The coding agent starts the run via the MCP run tool — the common path —
|
|
7
|
+
* whose result carries the id as an `executionId` field
|
|
8
|
+
* (`"executionId":"<id>"` / `executionId: <id>`).
|
|
9
|
+
* The detector is fed the SAME tool.call output PortDetector is (server/index.ts
|
|
10
|
+
* `onNormalizedEvent`, including the tool RESPONSE summary), so an MCP-triggered
|
|
11
|
+
* run is caught without changing how runs are triggered. It fires `onExecution`
|
|
12
|
+
* once per distinct (session, executionId); the integrator wires that to
|
|
13
|
+
* broadcast `{ type: "execution.started", … }` on /ws/events.
|
|
14
|
+
*
|
|
15
|
+
* Same streaming-safe design as PortDetector: a match touching the very end of
|
|
16
|
+
* the buffered text is held back (the id may still be growing in the next
|
|
17
|
+
* chunk) and finalized by `flush()`, which the integrator calls right after
|
|
18
|
+
* feeding a discrete, already-complete string (one tool.call field).
|
|
19
|
+
*/
|
|
20
|
+
/** Execution ids are the stable handles the executions API mints (e.g.
|
|
21
|
+
* `exec_ab12…`). Id-safe characters only, so a trailing space, newline, or
|
|
22
|
+
* ANSI reset ends the match rather than being captured into the id. */
|
|
23
|
+
const EXECUTION_ID = String.raw `[A-Za-z0-9_-]+`;
|
|
24
|
+
/** Two anchors, one capture group: the CLI's `Started execution <id>` line, or
|
|
25
|
+
* the `executionId` field from the run tool's structured result (quote/colon/
|
|
26
|
+
* space/equals between the key and the id). Both are start-time signals tied to
|
|
27
|
+
* a real run, so a match always refers to a run the user is actually driving. */
|
|
28
|
+
const EXECUTION_PATTERN_SOURCE = String.raw `(?:Started execution |executionId["'\s:=]+)(${EXECUTION_ID})`;
|
|
29
|
+
/** Long enough to hold either anchor ("Started execution " / `executionId":"`)
|
|
30
|
+
* split across a chunk boundary, plus a few id characters either side. */
|
|
31
|
+
const TAIL_SAFETY = 48;
|
|
32
|
+
/**
|
|
33
|
+
* Pure parser: every execution id announced in a chunk of CLI/tool output, in
|
|
34
|
+
* order (empty when the chunk announces none). This is the mutation-tested
|
|
35
|
+
* core — the {@link ExecutionDetector} class only adds streaming buffering and
|
|
36
|
+
* per-session dedupe on top of it.
|
|
37
|
+
*/
|
|
38
|
+
export function parseExecutionIds(text) {
|
|
39
|
+
const regex = new RegExp(EXECUTION_PATTERN_SOURCE, "g");
|
|
40
|
+
const ids = [];
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = regex.exec(text)))
|
|
43
|
+
ids.push(match[1]);
|
|
44
|
+
return ids;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Stateful, streaming-safe, and dedupes per (session, executionId) so a run id
|
|
48
|
+
* that reappears in output only ever fires `onExecution` once for the session's
|
|
49
|
+
* lifetime. `reset()` clears a session's state on exit so a reused session id
|
|
50
|
+
* starts clean.
|
|
51
|
+
*/
|
|
52
|
+
export class ExecutionDetector {
|
|
53
|
+
constructor(deps) {
|
|
54
|
+
this.deps = deps;
|
|
55
|
+
this.buffers = new Map();
|
|
56
|
+
this.seen = new Map();
|
|
57
|
+
}
|
|
58
|
+
feed(chunk, harnessSessionId) {
|
|
59
|
+
const combined = (this.buffers.get(harnessSessionId) ?? "") + chunk;
|
|
60
|
+
const regex = new RegExp(EXECUTION_PATTERN_SOURCE, "g");
|
|
61
|
+
let match;
|
|
62
|
+
let pendingFrom = null;
|
|
63
|
+
while ((match = regex.exec(combined))) {
|
|
64
|
+
const end = match.index + match[0].length;
|
|
65
|
+
if (end === combined.length) {
|
|
66
|
+
// The id could still be growing (more chars due next chunk) — hold back.
|
|
67
|
+
pendingFrom = match.index;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
this.tryEmit(harnessSessionId, match[1]);
|
|
71
|
+
}
|
|
72
|
+
const retainFrom = pendingFrom ?? Math.max(0, combined.length - TAIL_SAFETY);
|
|
73
|
+
this.buffers.set(harnessSessionId, combined.slice(retainFrom));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Finalizes whatever's held back for a session, immediately. Call this right
|
|
77
|
+
* after `feed()` whenever the fed text is a discrete, complete string (one
|
|
78
|
+
* tool.call field): without it, an id landing at the very end — the common
|
|
79
|
+
* shape "✓ Started execution exec_123" — would be held pending forever, since
|
|
80
|
+
* no "next chunk" is coming.
|
|
81
|
+
*/
|
|
82
|
+
flush(harnessSessionId) {
|
|
83
|
+
const pending = this.buffers.get(harnessSessionId);
|
|
84
|
+
if (!pending)
|
|
85
|
+
return;
|
|
86
|
+
// Re-parse the whole pending buffer (vs an inline loop) — same result, and
|
|
87
|
+
// tryEmit dedupes so a match already emitted by feed() is a harmless no-op.
|
|
88
|
+
for (const id of parseExecutionIds(pending))
|
|
89
|
+
this.tryEmit(harnessSessionId, id);
|
|
90
|
+
this.buffers.delete(harnessSessionId);
|
|
91
|
+
}
|
|
92
|
+
/** Drops buffered/dedupe state for a session — call on session exit so a
|
|
93
|
+
* reused session id starts clean. */
|
|
94
|
+
reset(harnessSessionId) {
|
|
95
|
+
this.buffers.delete(harnessSessionId);
|
|
96
|
+
this.seen.delete(harnessSessionId);
|
|
97
|
+
}
|
|
98
|
+
tryEmit(harnessSessionId, executionId) {
|
|
99
|
+
let ids = this.seen.get(harnessSessionId);
|
|
100
|
+
if (!ids) {
|
|
101
|
+
ids = new Set();
|
|
102
|
+
this.seen.set(harnessSessionId, ids);
|
|
103
|
+
}
|
|
104
|
+
if (ids.has(executionId))
|
|
105
|
+
return;
|
|
106
|
+
ids.add(executionId);
|
|
107
|
+
this.deps.onExecution(harnessSessionId, executionId, "prod");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=execution-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-detector.js","sourceRoot":"","sources":["../../src/core/execution-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"mcp-config.d.ts","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;iFAC6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
|
|
@@ -25,7 +25,15 @@ export async function generateMcpConfig(harnessSessionId, options = {}) {
|
|
|
25
25
|
},
|
|
26
26
|
"sapiom-dev": {
|
|
27
27
|
command: "npx",
|
|
28
|
-
|
|
28
|
+
// Pin the dist-tag (`@latest`) rather than the bare name so npx always
|
|
29
|
+
// resolves the PUBLISHED package from the registry. A bare
|
|
30
|
+
// `@sapiom/mcp` resolves a LOCAL workspace copy whenever the harness
|
|
31
|
+
// runs from inside the sapiom-js monorepo (dogfooding/dev) — whose bin
|
|
32
|
+
// isn't linked, so the server fails to launch ("sapiom-mcp: command
|
|
33
|
+
// not found" → JSON-RPC -32000). A dist-tag spec forces registry
|
|
34
|
+
// resolution and is behaviourally identical to what a real user
|
|
35
|
+
// (outside the monorepo) already gets, so it's a pure robustness fix.
|
|
36
|
+
args: ["-y", "@sapiom/mcp@latest"],
|
|
29
37
|
...(devEnv ? { env: devEnv } : {}),
|
|
30
38
|
},
|
|
31
39
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAoBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,gBAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChF,MAAM,MAAM,GAAuC,iBAAiB;QAClE,CAAC,CAAC,EAAE,kBAAkB,EAAE,iBAAiB,EAAE;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG;QACb,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,8BAA8B;gBACnC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,KAAK;gBACd,IAAI,EAAE,CAAC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"mcp-config.js","sourceRoot":"","sources":["../../../src/core/inject/mcp-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAoBhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,gBAAwB,EACxB,UAA4B,EAAE;IAE9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,aAAa,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC,CAAC;IACtG,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAChF,MAAM,MAAM,GAAuC,iBAAiB;QAClE,CAAC,CAAC,EAAE,kBAAkB,EAAE,iBAAiB,EAAE;QAC3C,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,MAAM,GAAG;QACb,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,MAAM;gBACZ,GAAG,EAAE,8BAA8B;gBACnC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE;YACD,YAAY,EAAE;gBACZ,OAAO,EAAE,KAAK;gBACd,uEAAuE;gBACvE,2DAA2D;gBAC3D,qEAAqE;gBACrE,uEAAuE;gBACvE,oEAAoE;gBACpE,iEAAiE;gBACjE,gEAAgE;gBAChE,sEAAsE;gBACtE,IAAI,EAAE,CAAC,IAAI,EAAE,oBAAoB,CAAC;gBAClC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnC;SACF;KACF,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IACnD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtF,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ExecutionProjection } from "@sapiom/agent-core";
|
|
2
|
+
import type { RunView } from "../shared/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Map a decoded {@link ExecutionProjection} to the {@link RunView} the canvas
|
|
5
|
+
* renders. Order-preserving over `steps`; total (never throws) because the
|
|
6
|
+
* projection is already the decoded, degradation-tolerant shape.
|
|
7
|
+
*/
|
|
8
|
+
export declare function renderRunState(decoded: ExecutionProjection): RunView;
|
|
9
|
+
//# sourceMappingURL=render-run-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-run-state.d.ts","sourceRoot":"","sources":["../../src/core/render-run-state.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,OAAO,EAAwB,MAAM,oBAAoB,CAAC;AAoHxE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAMpE"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* renderRunState — the ONE pure mapper from a decoded {@link ExecutionProjection}
|
|
3
|
+
* to the canvas's {@link RunView} render state.
|
|
4
|
+
*
|
|
5
|
+
* Pure and deterministic: no LLM, no I/O, no clock. Every field is derived from
|
|
6
|
+
* the decoded projection, so the same function drives the polling path today and
|
|
7
|
+
* a future WebSocket push (only the data source swaps, never this mapping). Cost
|
|
8
|
+
* and latency are read straight from the projection and rendered statically — the
|
|
9
|
+
* LLM is only ever invoked later by an explicit debug-macro press, never to
|
|
10
|
+
* compute what's shown here.
|
|
11
|
+
*
|
|
12
|
+
* Honest absence is preserved end to end: a step with no cost on this read gets
|
|
13
|
+
* no `costUsd` (not `0`), a still-running step gets no `latencyMs`, and a step
|
|
14
|
+
* with no logs gets no `logSlice` — matching the SDK's "null cost is honest
|
|
15
|
+
* absence, never a fabricated $0" contract.
|
|
16
|
+
*/
|
|
17
|
+
import { isExecutionTerminal } from "@sapiom/agent-core";
|
|
18
|
+
/**
|
|
19
|
+
* Cap on the characters of a step's executor log buffer surfaced in `logSlice`.
|
|
20
|
+
* The TAIL is kept (most recent lines) because failures surface at the end of a
|
|
21
|
+
* log. This is a payload guard on the poll response; the debug-macro context
|
|
22
|
+
* extractor does the final, smaller trim for prompt injection.
|
|
23
|
+
*/
|
|
24
|
+
const LOG_SLICE_MAX = 4000;
|
|
25
|
+
/**
|
|
26
|
+
* Fold the run's lifecycle status into the four states the UI distinguishes.
|
|
27
|
+
* Reuses `isExecutionTerminal` (agent-core's single source of truth for "won't
|
|
28
|
+
* advance on its own") so a newly-added terminal status can never be misdrawn as
|
|
29
|
+
* still-running: anything non-terminal (running, paused, queued, unknown) is
|
|
30
|
+
* `running`; the terminal set normalizes `canceled` → `cancelled`.
|
|
31
|
+
*/
|
|
32
|
+
function toRunStatus(raw) {
|
|
33
|
+
if (!isExecutionTerminal(raw))
|
|
34
|
+
return "running";
|
|
35
|
+
if (raw === "completed")
|
|
36
|
+
return "completed";
|
|
37
|
+
if (raw === "failed")
|
|
38
|
+
return "failed";
|
|
39
|
+
return "cancelled"; // the only remaining terminal statuses: "cancelled" / "canceled"
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fold a step's raw status into a {@link StepStatus}. Handles the real engine
|
|
43
|
+
* vocabulary (`succeeded`/`threw` from prod agents surface) plus the earlier
|
|
44
|
+
* values (`completed`/`failed`/`cancelled`/`canceled`). A cancelled or threw
|
|
45
|
+
* step folds to `failed` (it did not pass — mirrors run-local.ts); anything
|
|
46
|
+
* not yet running/passed/failed is `pending` (unstarted, queued, or unknown).
|
|
47
|
+
*/
|
|
48
|
+
function toStepStatus(raw) {
|
|
49
|
+
if (raw === "completed" || raw === "succeeded")
|
|
50
|
+
return "passed";
|
|
51
|
+
if (raw === "failed" ||
|
|
52
|
+
raw === "threw" ||
|
|
53
|
+
raw === "cancelled" ||
|
|
54
|
+
raw === "canceled")
|
|
55
|
+
return "failed";
|
|
56
|
+
if (raw === "running")
|
|
57
|
+
return "running";
|
|
58
|
+
return "pending";
|
|
59
|
+
}
|
|
60
|
+
/** Captured USD as a number; `undefined` on honest absence (null cost) or an
|
|
61
|
+
* unparseable amount — never a fabricated `0`. */
|
|
62
|
+
function toCostUsd(cost) {
|
|
63
|
+
if (!cost)
|
|
64
|
+
return undefined;
|
|
65
|
+
const n = Number(cost.capturedUsd);
|
|
66
|
+
return Number.isFinite(n) ? n : undefined;
|
|
67
|
+
}
|
|
68
|
+
/** `finishedAt − startedAt` in ms; `undefined` while still running (no finish)
|
|
69
|
+
* or when either timestamp is unparseable. A negative delta (clock skew) is
|
|
70
|
+
* dropped rather than shown as a misleading negative latency. */
|
|
71
|
+
function toLatencyMs(startedAt, finishedAt) {
|
|
72
|
+
if (!startedAt || !finishedAt)
|
|
73
|
+
return undefined;
|
|
74
|
+
const start = Date.parse(startedAt);
|
|
75
|
+
const end = Date.parse(finishedAt);
|
|
76
|
+
if (!Number.isFinite(start) || !Number.isFinite(end))
|
|
77
|
+
return undefined;
|
|
78
|
+
const delta = end - start;
|
|
79
|
+
return delta >= 0 ? delta : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** One executor log entry → a compact line. Accepts the `{ ts, level, msg }`
|
|
82
|
+
* wire shape (or `message`), a bare string, or anything else (stringified). */
|
|
83
|
+
function formatLogEntry(entry) {
|
|
84
|
+
if (typeof entry === "string")
|
|
85
|
+
return entry;
|
|
86
|
+
if (entry !== null && typeof entry === "object") {
|
|
87
|
+
const e = entry;
|
|
88
|
+
const parts = [e.ts, e.level, e.msg ?? e.message].filter((p) => typeof p === "string" || typeof p === "number");
|
|
89
|
+
if (parts.length > 0)
|
|
90
|
+
return parts.map(String).join(" ");
|
|
91
|
+
}
|
|
92
|
+
return String(entry);
|
|
93
|
+
}
|
|
94
|
+
/** Format the executor log buffer into a trimmed, tail-preserving slice, or
|
|
95
|
+
* `undefined` when there are no usable logs. */
|
|
96
|
+
function toLogSlice(logs) {
|
|
97
|
+
if (!Array.isArray(logs) || logs.length === 0)
|
|
98
|
+
return undefined;
|
|
99
|
+
const text = logs.map(formatLogEntry).join("\n").trim();
|
|
100
|
+
if (text === "")
|
|
101
|
+
return undefined;
|
|
102
|
+
return text.length > LOG_SLICE_MAX
|
|
103
|
+
? text.slice(text.length - LOG_SLICE_MAX)
|
|
104
|
+
: text;
|
|
105
|
+
}
|
|
106
|
+
/** Map one projection step to its render view. Optional fields are assigned only
|
|
107
|
+
* when present so absence stays absent (not `undefined`) in the JSON payload. */
|
|
108
|
+
function toStepView(step) {
|
|
109
|
+
const view = {
|
|
110
|
+
id: step.spanId ?? `step-${step.stepOrder}-${step.attempt}`,
|
|
111
|
+
name: step.stepName,
|
|
112
|
+
status: toStepStatus(step.status),
|
|
113
|
+
};
|
|
114
|
+
const costUsd = toCostUsd(step.cost);
|
|
115
|
+
if (costUsd !== undefined)
|
|
116
|
+
view.costUsd = costUsd;
|
|
117
|
+
const latencyMs = toLatencyMs(step.startedAt, step.finishedAt);
|
|
118
|
+
if (latencyMs !== undefined)
|
|
119
|
+
view.latencyMs = latencyMs;
|
|
120
|
+
if (step.error?.message)
|
|
121
|
+
view.error = step.error.message;
|
|
122
|
+
const logSlice = toLogSlice(step.logs);
|
|
123
|
+
if (logSlice !== undefined)
|
|
124
|
+
view.logSlice = logSlice;
|
|
125
|
+
return view;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Map a decoded {@link ExecutionProjection} to the {@link RunView} the canvas
|
|
129
|
+
* renders. Order-preserving over `steps`; total (never throws) because the
|
|
130
|
+
* projection is already the decoded, degradation-tolerant shape.
|
|
131
|
+
*/
|
|
132
|
+
export function renderRunState(decoded) {
|
|
133
|
+
return {
|
|
134
|
+
executionId: decoded.id,
|
|
135
|
+
status: toRunStatus(decoded.status),
|
|
136
|
+
steps: decoded.steps.map(toStepView),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=render-run-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-run-state.js","sourceRoot":"","sources":["../../src/core/render-run-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-spend — fetches the cost/spend summary for a prod execution from the
|
|
3
|
+
* core surface and maps it to a {@link RunSpend} the canvas can poll.
|
|
4
|
+
*
|
|
5
|
+
* WHY the core surface: the spend endpoint lives at
|
|
6
|
+
* `GET /v1/workflows/executions/:id/spend` on api.sapiom.ai (the CORE
|
|
7
|
+
* capability surface), which is distinct from the agents surface
|
|
8
|
+
* (tools.sapiom.ai) that run-state.ts uses. The same caveat applies:
|
|
9
|
+
* env-consistency with the run's environment matters — both surfaces must be
|
|
10
|
+
* overridden together when pointing at non-prod.
|
|
11
|
+
*
|
|
12
|
+
* WHY the key stays server-side: same rationale as run-state.ts — the Sapiom
|
|
13
|
+
* API key is a harness credential that must never reach the browser. The
|
|
14
|
+
* harness server fetches on behalf of the canvas, so the SPA polls a local
|
|
15
|
+
* `/api/runs/:id/spend` endpoint (no key in the request) rather than calling
|
|
16
|
+
* the upstream surface directly.
|
|
17
|
+
*/
|
|
18
|
+
import type { RunSpend } from "../shared/types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the CORE surface base URL for the spend endpoint.
|
|
21
|
+
*
|
|
22
|
+
* The spend endpoint lives on the CORE host (`api.<env>`), distinct from the
|
|
23
|
+
* agents host run-state.ts uses (`tools.<env>`). Crucially, a run lives in the
|
|
24
|
+
* agents env, and its spend must be read from the MATCHING core env — otherwise
|
|
25
|
+
* a prod run's spend queried against dev 401s. So we DERIVE the core host from
|
|
26
|
+
* the agents host (`tools.<env>` → `api.<env>`) rather than reading
|
|
27
|
+
* `SAPIOM_API_URL`, which in some setups points at a different env than the
|
|
28
|
+
* agents surface. An explicit `SAPIOM_CORE_URL` still wins for full control.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveCoreBaseUrl(): string;
|
|
31
|
+
export type RunSpendResult = {
|
|
32
|
+
ok: true;
|
|
33
|
+
spend: RunSpend;
|
|
34
|
+
} | {
|
|
35
|
+
ok: false;
|
|
36
|
+
status: number;
|
|
37
|
+
error: string;
|
|
38
|
+
};
|
|
39
|
+
export interface RunSpendFetcherOpts {
|
|
40
|
+
apiKey: string | null;
|
|
41
|
+
baseUrl?: string;
|
|
42
|
+
/** Injectable fetch implementation — defaults to global fetch. Test seam. */
|
|
43
|
+
fetchImpl?: typeof fetch;
|
|
44
|
+
}
|
|
45
|
+
export interface RunSpendFetcher {
|
|
46
|
+
fetch(executionId: string): Promise<RunSpendResult>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a fetcher that resolves an execution's spend/cost from the core
|
|
50
|
+
* surface. The fetcher is intentionally non-throwing: all error paths return
|
|
51
|
+
* a typed {@link RunSpendResult} with `ok: false` so the router can forward
|
|
52
|
+
* the appropriate HTTP status without a try/catch at the call site.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createRunSpendFetcher(opts: RunSpendFetcherOpts): RunSpendFetcher;
|
|
55
|
+
//# sourceMappingURL=run-spend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-spend.d.ts","sourceRoot":"","sources":["../../src/core/run-spend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAc3C;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GACxB,eAAe,CAsEjB"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-spend — fetches the cost/spend summary for a prod execution from the
|
|
3
|
+
* core surface and maps it to a {@link RunSpend} the canvas can poll.
|
|
4
|
+
*
|
|
5
|
+
* WHY the core surface: the spend endpoint lives at
|
|
6
|
+
* `GET /v1/workflows/executions/:id/spend` on api.sapiom.ai (the CORE
|
|
7
|
+
* capability surface), which is distinct from the agents surface
|
|
8
|
+
* (tools.sapiom.ai) that run-state.ts uses. The same caveat applies:
|
|
9
|
+
* env-consistency with the run's environment matters — both surfaces must be
|
|
10
|
+
* overridden together when pointing at non-prod.
|
|
11
|
+
*
|
|
12
|
+
* WHY the key stays server-side: same rationale as run-state.ts — the Sapiom
|
|
13
|
+
* API key is a harness credential that must never reach the browser. The
|
|
14
|
+
* harness server fetches on behalf of the canvas, so the SPA polls a local
|
|
15
|
+
* `/api/runs/:id/spend` endpoint (no key in the request) rather than calling
|
|
16
|
+
* the upstream surface directly.
|
|
17
|
+
*/
|
|
18
|
+
import { resolveAgentsBaseUrl } from "./run-state.js";
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the CORE surface base URL for the spend endpoint.
|
|
21
|
+
*
|
|
22
|
+
* The spend endpoint lives on the CORE host (`api.<env>`), distinct from the
|
|
23
|
+
* agents host run-state.ts uses (`tools.<env>`). Crucially, a run lives in the
|
|
24
|
+
* agents env, and its spend must be read from the MATCHING core env — otherwise
|
|
25
|
+
* a prod run's spend queried against dev 401s. So we DERIVE the core host from
|
|
26
|
+
* the agents host (`tools.<env>` → `api.<env>`) rather than reading
|
|
27
|
+
* `SAPIOM_API_URL`, which in some setups points at a different env than the
|
|
28
|
+
* agents surface. An explicit `SAPIOM_CORE_URL` still wins for full control.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveCoreBaseUrl() {
|
|
31
|
+
const override = process.env.SAPIOM_CORE_URL;
|
|
32
|
+
if (override)
|
|
33
|
+
return override;
|
|
34
|
+
const agents = resolveAgentsBaseUrl();
|
|
35
|
+
try {
|
|
36
|
+
const url = new URL(agents);
|
|
37
|
+
if (url.hostname.startsWith("tools.")) {
|
|
38
|
+
url.hostname = `api.${url.hostname.slice("tools.".length)}`;
|
|
39
|
+
return url.origin;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Unparseable agents URL — fall through to the prod default.
|
|
44
|
+
}
|
|
45
|
+
return "https://api.sapiom.ai";
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a fetcher that resolves an execution's spend/cost from the core
|
|
49
|
+
* surface. The fetcher is intentionally non-throwing: all error paths return
|
|
50
|
+
* a typed {@link RunSpendResult} with `ok: false` so the router can forward
|
|
51
|
+
* the appropriate HTTP status without a try/catch at the call site.
|
|
52
|
+
*/
|
|
53
|
+
export function createRunSpendFetcher(opts) {
|
|
54
|
+
const { apiKey, baseUrl = resolveCoreBaseUrl(), fetchImpl = fetch } = opts;
|
|
55
|
+
return {
|
|
56
|
+
async fetch(executionId) {
|
|
57
|
+
// No API key — do not touch the network; the harness is not signed in.
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
status: 503,
|
|
62
|
+
error: "harness is not signed in to Sapiom",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
let res;
|
|
66
|
+
try {
|
|
67
|
+
res = await fetchImpl(`${baseUrl}/v1/workflows/executions/${encodeURIComponent(executionId)}/spend`, { headers: { "x-api-key": apiKey } });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { ok: false, status: 502, error: "gateway unreachable" };
|
|
71
|
+
}
|
|
72
|
+
if (res.status === 404) {
|
|
73
|
+
return { ok: false, status: 404, error: "spend not found" };
|
|
74
|
+
}
|
|
75
|
+
if (!res.ok) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
status: 502,
|
|
79
|
+
error: `gateway responded ${res.status}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const raw = (await res.json());
|
|
84
|
+
// The response is ExecutionSpendDto — we map the capability sub-object
|
|
85
|
+
// fields to RunSpend, falling back to top-level totalUsd when needed.
|
|
86
|
+
const capability = raw.capability;
|
|
87
|
+
const totalUsd = capability?.totalUsd ??
|
|
88
|
+
raw.totalUsd ??
|
|
89
|
+
"0";
|
|
90
|
+
const settleState = capability?.settleState ?? "pending";
|
|
91
|
+
const rawByStep = capability?.byStep ?? [];
|
|
92
|
+
const byStep = rawByStep.map((s) => {
|
|
93
|
+
const step = s;
|
|
94
|
+
return {
|
|
95
|
+
name: step.stepName,
|
|
96
|
+
totalUsd: step.totalUsd,
|
|
97
|
+
entryCount: step.entryCount,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
const spend = { executionId, totalUsd, settleState, byStep };
|
|
101
|
+
return { ok: true, spend };
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { ok: false, status: 502, error: "could not decode spend" };
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=run-spend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-spend.js","sourceRoot":"","sources":["../../src/core/run-spend.ts"],"names":[],"mappings":"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"}
|