@sapiom/agent-core 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +246 -0
  2. package/LICENSE +21 -0
  3. package/dist/cjs/__tests__/execution-detail.wire.fixture.json +101 -0
  4. package/dist/cjs/__tests__/execution-projection.preseam.fixture.json +37 -0
  5. package/dist/cjs/__tests__/execution-projection.with-cost.fixture.json +60 -0
  6. package/dist/cjs/bundle.d.ts +5 -0
  7. package/dist/cjs/bundle.js +134 -0
  8. package/dist/cjs/check.d.ts +10 -0
  9. package/dist/cjs/check.js +154 -0
  10. package/dist/cjs/client.d.ts +21 -0
  11. package/dist/cjs/client.js +108 -0
  12. package/dist/cjs/clone.d.ts +17 -0
  13. package/dist/cjs/clone.js +71 -0
  14. package/dist/cjs/config.d.ts +16 -0
  15. package/dist/cjs/config.js +40 -0
  16. package/dist/cjs/decode.d.ts +4 -0
  17. package/dist/cjs/decode.js +172 -0
  18. package/dist/cjs/deploy.d.ts +12 -0
  19. package/dist/cjs/deploy.js +67 -0
  20. package/dist/cjs/errors.d.ts +15 -0
  21. package/dist/cjs/errors.js +23 -0
  22. package/dist/cjs/git.d.ts +12 -0
  23. package/dist/cjs/git.js +76 -0
  24. package/dist/cjs/index.d.ts +41 -0
  25. package/dist/cjs/index.js +72 -0
  26. package/dist/cjs/inspect.d.ts +40 -0
  27. package/dist/cjs/inspect.js +99 -0
  28. package/dist/cjs/link.d.ts +15 -0
  29. package/dist/cjs/link.js +19 -0
  30. package/dist/cjs/local/dispatcher.d.ts +37 -0
  31. package/dist/cjs/local/dispatcher.js +149 -0
  32. package/dist/cjs/local/load.d.ts +6 -0
  33. package/dist/cjs/local/load.js +102 -0
  34. package/dist/cjs/local/run-local.d.ts +32 -0
  35. package/dist/cjs/local/run-local.js +95 -0
  36. package/dist/cjs/local/stubs.d.ts +8 -0
  37. package/dist/cjs/local/stubs.js +35 -0
  38. package/dist/cjs/run.d.ts +11 -0
  39. package/dist/cjs/run.js +29 -0
  40. package/dist/cjs/scaffold.d.ts +24 -0
  41. package/dist/cjs/scaffold.js +190 -0
  42. package/dist/cjs/schedule.d.ts +67 -0
  43. package/dist/cjs/schedule.js +33 -0
  44. package/dist/cjs/signal.d.ts +12 -0
  45. package/dist/cjs/signal.js +24 -0
  46. package/dist/cjs/types.d.ts +99 -0
  47. package/dist/cjs/types.js +11 -0
  48. package/dist/cjs/watch.d.ts +10 -0
  49. package/dist/cjs/watch.js +101 -0
  50. package/dist/esm/__tests__/execution-detail.wire.fixture.json +101 -0
  51. package/dist/esm/__tests__/execution-projection.preseam.fixture.json +37 -0
  52. package/dist/esm/__tests__/execution-projection.with-cost.fixture.json +60 -0
  53. package/dist/esm/bundle.d.ts +5 -0
  54. package/dist/esm/bundle.js +95 -0
  55. package/dist/esm/check.d.ts +10 -0
  56. package/dist/esm/check.js +115 -0
  57. package/dist/esm/client.d.ts +21 -0
  58. package/dist/esm/client.js +103 -0
  59. package/dist/esm/clone.d.ts +17 -0
  60. package/dist/esm/clone.js +65 -0
  61. package/dist/esm/config.d.ts +16 -0
  62. package/dist/esm/config.js +31 -0
  63. package/dist/esm/decode.d.ts +4 -0
  64. package/dist/esm/decode.js +167 -0
  65. package/dist/esm/deploy.d.ts +12 -0
  66. package/dist/esm/deploy.js +61 -0
  67. package/dist/esm/errors.d.ts +15 -0
  68. package/dist/esm/errors.js +19 -0
  69. package/dist/esm/git.d.ts +12 -0
  70. package/dist/esm/git.js +69 -0
  71. package/dist/esm/index.d.ts +41 -0
  72. package/dist/esm/index.js +21 -0
  73. package/dist/esm/inspect.d.ts +40 -0
  74. package/dist/esm/inspect.js +92 -0
  75. package/dist/esm/link.d.ts +15 -0
  76. package/dist/esm/link.js +16 -0
  77. package/dist/esm/local/dispatcher.d.ts +37 -0
  78. package/dist/esm/local/dispatcher.js +145 -0
  79. package/dist/esm/local/load.d.ts +6 -0
  80. package/dist/esm/local/load.js +63 -0
  81. package/dist/esm/local/run-local.d.ts +32 -0
  82. package/dist/esm/local/run-local.js +87 -0
  83. package/dist/esm/local/stubs.d.ts +8 -0
  84. package/dist/esm/local/stubs.js +31 -0
  85. package/dist/esm/package.json +1 -0
  86. package/dist/esm/run.d.ts +11 -0
  87. package/dist/esm/run.js +25 -0
  88. package/dist/esm/scaffold.d.ts +24 -0
  89. package/dist/esm/scaffold.js +179 -0
  90. package/dist/esm/schedule.d.ts +67 -0
  91. package/dist/esm/schedule.js +26 -0
  92. package/dist/esm/signal.d.ts +12 -0
  93. package/dist/esm/signal.js +20 -0
  94. package/dist/esm/types.d.ts +99 -0
  95. package/dist/esm/types.js +8 -0
  96. package/dist/esm/watch.d.ts +10 -0
  97. package/dist/esm/watch.js +96 -0
  98. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  99. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  100. package/package.json +73 -0
  101. package/templates/coding-pause/AGENTS.md +48 -0
  102. package/templates/coding-pause/CLAUDE.md +7 -0
  103. package/templates/coding-pause/README.md +53 -0
  104. package/templates/coding-pause/_gitignore +3 -0
  105. package/templates/coding-pause/index.ts +109 -0
  106. package/templates/coding-pause/package.json +21 -0
  107. package/templates/coding-pause/tsconfig.json +12 -0
  108. package/templates/default/AGENTS.md +48 -0
  109. package/templates/default/CLAUDE.md +7 -0
  110. package/templates/default/README.md +30 -0
  111. package/templates/default/_gitignore +3 -0
  112. package/templates/default/index.ts +26 -0
  113. package/templates/default/package.json +21 -0
  114. package/templates/default/tsconfig.json +12 -0
@@ -0,0 +1,115 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertValidGraph, buildManifest, isAgentDefinition, agentManifestSchema } from '@sapiom/agent';
7
+ import * as esbuild from 'esbuild';
8
+ import { AgentOperationError } from './errors.js';
9
+ function runTypecheck(sourceDir) {
10
+ const tscBin = path.join(sourceDir, 'node_modules', '.bin', 'tsc');
11
+ if (!existsSync(tscBin)) {
12
+ return 'typecheck skipped — TypeScript is not installed (run npm install first)';
13
+ }
14
+ try {
15
+ execFileSync(tscBin, ['--noEmit'], { cwd: sourceDir, stdio: ['ignore', 'pipe', 'pipe'] });
16
+ return null;
17
+ }
18
+ catch (err) {
19
+ const e = err;
20
+ const output = (e.stdout?.toString() ?? '').trim() || (e.stderr?.toString() ?? '').trim();
21
+ throw new AgentOperationError({
22
+ code: 'TYPECHECK_FAILED',
23
+ message: 'The agent has type errors.',
24
+ hint: output || 'Run `tsc --noEmit` for details.',
25
+ });
26
+ }
27
+ }
28
+ const LOCAL_SDK_VERSION = '0.0.0-local';
29
+ export async function check(opts) {
30
+ const { sourceDir } = opts;
31
+ const entryFile = path.join(sourceDir, 'index.ts');
32
+ if (!existsSync(entryFile)) {
33
+ throw new AgentOperationError({
34
+ code: 'NO_ENTRY',
35
+ message: `No index.ts found in ${sourceDir}.`,
36
+ hint: 'Run this from an agent project, or pass its directory.',
37
+ });
38
+ }
39
+ const warnings = [];
40
+ const typecheckSkip = runTypecheck(sourceDir);
41
+ if (typecheckSkip)
42
+ warnings.push(typecheckSkip);
43
+ const tmp = mkdtempSync(path.join(tmpdir(), 'sapiom-check-'));
44
+ const bundlePath = path.join(tmp, 'definition.mjs');
45
+ try {
46
+ try {
47
+ await esbuild.build({
48
+ entryPoints: [entryFile],
49
+ outfile: bundlePath,
50
+ bundle: true,
51
+ platform: 'node',
52
+ target: 'node20',
53
+ format: 'esm',
54
+ logLevel: 'silent',
55
+ });
56
+ }
57
+ catch (err) {
58
+ throw new AgentOperationError({
59
+ code: 'BUNDLE_FAILED',
60
+ message: 'Failed to bundle the agent.',
61
+ hint: err instanceof Error ? err.message : String(err),
62
+ });
63
+ }
64
+ const mod = await import(`file://${bundlePath}?t=${Date.now()}`);
65
+ const defs = [];
66
+ for (const value of Object.values(mod)) {
67
+ if (isAgentDefinition(value) && !defs.includes(value))
68
+ defs.push(value);
69
+ }
70
+ if (defs.length === 0) {
71
+ throw new AgentOperationError({
72
+ code: 'NO_DEFINITION',
73
+ message: 'No agent was exported from index.ts.',
74
+ hint: 'Export the result of defineAgent({ … }).',
75
+ });
76
+ }
77
+ if (defs.length > 1) {
78
+ throw new AgentOperationError({
79
+ code: 'MULTIPLE_DEFINITIONS',
80
+ message: 'index.ts exports more than one agent.',
81
+ hint: 'Export exactly one defineAgent({ … }) result.',
82
+ });
83
+ }
84
+ const def = defs[0];
85
+ const sha256 = createHash('sha256').update(readFileSync(bundlePath)).digest('hex');
86
+ let manifest;
87
+ try {
88
+ manifest = agentManifestSchema.parse(buildManifest(def, { sdkVersion: LOCAL_SDK_VERSION, artifact: { sha256, entryFile: 'definition.mjs' } }));
89
+ }
90
+ catch (err) {
91
+ throw new AgentOperationError({
92
+ code: 'MANIFEST_INVALID',
93
+ message: 'The agent produced an invalid manifest.',
94
+ hint: err instanceof Error ? err.message : String(err),
95
+ });
96
+ }
97
+ try {
98
+ warnings.push(...assertValidGraph(manifest));
99
+ }
100
+ catch (err) {
101
+ throw new AgentOperationError({
102
+ code: 'GRAPH_INVALID',
103
+ message: 'The agent graph is invalid.',
104
+ hint: err instanceof Error ? err.message : String(err),
105
+ });
106
+ }
107
+ const steps = manifest.steps;
108
+ const stepCount = Array.isArray(steps) ? steps.length : Object.keys(steps ?? {}).length;
109
+ const name = manifest.name ?? 'agent';
110
+ return { name, stepCount, warnings, manifest };
111
+ }
112
+ finally {
113
+ rmSync(tmp, { recursive: true, force: true });
114
+ }
115
+ }
@@ -0,0 +1,21 @@
1
+ export declare const DEFAULT_WORKFLOWS_HOST = "https://api.sapiom.ai";
2
+ export interface ClientOptions {
3
+ host?: string;
4
+ apiKey: string;
5
+ }
6
+ export interface GatewayErrorBody {
7
+ message?: string | string[];
8
+ }
9
+ export declare class GatewayClient {
10
+ private readonly base;
11
+ private readonly apiKey;
12
+ constructor(opts: ClientOptions);
13
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
14
+ get<T = unknown>(path: string): Promise<T>;
15
+ post<T = unknown>(path: string, body?: unknown): Promise<T>;
16
+ openStream(path: string, opts?: {
17
+ signal?: AbortSignal;
18
+ lastEventId?: string;
19
+ }): Promise<Response>;
20
+ }
21
+ export declare function createClient(opts: ClientOptions): GatewayClient;
@@ -0,0 +1,103 @@
1
+ import { AgentOperationError } from './errors.js';
2
+ export const DEFAULT_WORKFLOWS_HOST = 'https://api.sapiom.ai';
3
+ export class GatewayClient {
4
+ constructor(opts) {
5
+ const host = (opts.host ?? DEFAULT_WORKFLOWS_HOST).replace(/\/$/, '');
6
+ this.base = `${host}/v1/workflows`;
7
+ this.apiKey = opts.apiKey;
8
+ }
9
+ async request(method, path, body) {
10
+ let res;
11
+ try {
12
+ res = await fetch(`${this.base}${path}`, {
13
+ method,
14
+ headers: { 'x-api-key': this.apiKey, 'content-type': 'application/json' },
15
+ body: body === undefined ? undefined : JSON.stringify(body),
16
+ });
17
+ }
18
+ catch (err) {
19
+ throw new AgentOperationError({
20
+ code: 'NETWORK',
21
+ message: `Could not reach ${this.base}.`,
22
+ hint: err instanceof Error ? err.message : String(err),
23
+ });
24
+ }
25
+ const text = await res.text();
26
+ const data = text ? safeParse(text) : undefined;
27
+ if (!res.ok) {
28
+ throw new AgentOperationError({
29
+ code: `HTTP_${res.status}`,
30
+ message: messageFrom(data) ?? `Request failed (${res.status} ${res.statusText}).`,
31
+ hint: res.status === 401 || res.status === 403
32
+ ? 'Check your API key (`sapiom login` or SAPIOM_API_KEY) and that it has access to this agent.'
33
+ : undefined,
34
+ });
35
+ }
36
+ return data;
37
+ }
38
+ get(path) {
39
+ return this.request('GET', path);
40
+ }
41
+ post(path, body) {
42
+ return this.request('POST', path, body);
43
+ }
44
+ async openStream(path, opts = {}) {
45
+ const headers = {
46
+ 'x-api-key': this.apiKey,
47
+ accept: 'text/event-stream',
48
+ };
49
+ if (opts.lastEventId) {
50
+ headers['last-event-id'] = opts.lastEventId;
51
+ }
52
+ let res;
53
+ try {
54
+ res = await fetch(`${this.base}${path}`, { method: 'GET', headers, signal: opts.signal });
55
+ }
56
+ catch (err) {
57
+ throw new AgentOperationError({
58
+ code: 'NETWORK',
59
+ message: `Could not reach ${this.base}.`,
60
+ hint: err instanceof Error ? err.message : String(err),
61
+ });
62
+ }
63
+ if (!res.ok) {
64
+ const text = await res.text().catch(() => '');
65
+ const data = text ? safeParse(text) : undefined;
66
+ throw new AgentOperationError({
67
+ code: `HTTP_${res.status}`,
68
+ message: messageFrom(data) ?? `Stream request failed (${res.status} ${res.statusText}).`,
69
+ hint: res.status === 401 || res.status === 403
70
+ ? 'Check your API key (`sapiom login` or SAPIOM_API_KEY) and that it has access to this agent.'
71
+ : undefined,
72
+ });
73
+ }
74
+ if (!res.body) {
75
+ throw new AgentOperationError({
76
+ code: 'NETWORK',
77
+ message: `Stream at ${this.base}${path} returned no body.`,
78
+ });
79
+ }
80
+ return res;
81
+ }
82
+ }
83
+ export function createClient(opts) {
84
+ return new GatewayClient(opts);
85
+ }
86
+ function safeParse(text) {
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch {
91
+ return text;
92
+ }
93
+ }
94
+ function messageFrom(data) {
95
+ if (data && typeof data === 'object' && 'message' in data) {
96
+ const m = data.message;
97
+ if (Array.isArray(m))
98
+ return m.join('; ');
99
+ if (typeof m === 'string')
100
+ return m;
101
+ }
102
+ return undefined;
103
+ }
@@ -0,0 +1,17 @@
1
+ import { GatewayClient } from './client.js';
2
+ import { type CloneRepoOptions } from './git.js';
3
+ export interface CloneOptions {
4
+ templateId?: string;
5
+ forkId?: string;
6
+ targetDir: string;
7
+ cloneRepo?: (opts: CloneRepoOptions) => void;
8
+ }
9
+ export interface CloneResult {
10
+ forkId: string;
11
+ templateId?: string;
12
+ repoFullName: string;
13
+ defaultBranch: string;
14
+ targetDir: string;
15
+ tokenExpiresAt: string;
16
+ }
17
+ export declare function clone(opts: CloneOptions, client: GatewayClient): Promise<CloneResult>;
@@ -0,0 +1,65 @@
1
+ import { existsSync, mkdirSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { writeConfig } from './config.js';
4
+ import { AgentOperationError } from './errors.js';
5
+ import { cloneRepo as defaultCloneRepo } from './git.js';
6
+ export async function clone(opts, client) {
7
+ const { templateId, forkId, targetDir } = opts;
8
+ const runClone = opts.cloneRepo ?? defaultCloneRepo;
9
+ if (!templateId && !forkId) {
10
+ throw new AgentOperationError({
11
+ code: 'BAD_INPUT',
12
+ message: 'Provide a templateId (to fork then clone) or a forkId (to clone an existing fork).',
13
+ });
14
+ }
15
+ if (templateId && forkId) {
16
+ throw new AgentOperationError({
17
+ code: 'BAD_INPUT',
18
+ message: 'Provide only one of templateId or forkId, not both.',
19
+ hint: 'Use templateId to start from a gallery template, or forkId to re-clone an existing fork.',
20
+ });
21
+ }
22
+ if (existsSync(targetDir) && readdirSync(targetDir).length > 0) {
23
+ throw new AgentOperationError({
24
+ code: 'DIR_NOT_EMPTY',
25
+ message: `Target directory '${targetDir}' already exists and is not empty.`,
26
+ });
27
+ }
28
+ let resolvedForkId = forkId ?? '';
29
+ let resolvedTemplateId = templateId;
30
+ if (templateId) {
31
+ const fork = await client.post(`/templates/${encodeURIComponent(templateId)}/fork`, {});
32
+ resolvedForkId = fork.id;
33
+ resolvedTemplateId = fork.templateId;
34
+ }
35
+ const token = await client.post(`/forks/${encodeURIComponent(resolvedForkId)}/clone-token`, {});
36
+ if (!token.cloneUrl.startsWith('https://')) {
37
+ throw new AgentOperationError({
38
+ code: 'BAD_CLONE_URL',
39
+ message: 'The clone token endpoint returned an unexpected clone URL.',
40
+ });
41
+ }
42
+ const parent = path.dirname(path.resolve(targetDir));
43
+ mkdirSync(parent, { recursive: true });
44
+ runClone({
45
+ cloneUrl: token.cloneUrl,
46
+ targetDir,
47
+ branch: token.defaultBranch,
48
+ repoFullName: token.repoFullName,
49
+ cwd: parent,
50
+ });
51
+ writeConfig(targetDir, {
52
+ repoFullName: token.repoFullName,
53
+ defaultBranch: token.defaultBranch,
54
+ forkId: resolvedForkId,
55
+ ...(resolvedTemplateId ? { templateId: resolvedTemplateId } : {}),
56
+ });
57
+ return {
58
+ forkId: resolvedForkId,
59
+ ...(resolvedTemplateId ? { templateId: resolvedTemplateId } : {}),
60
+ repoFullName: token.repoFullName,
61
+ defaultBranch: token.defaultBranch,
62
+ targetDir,
63
+ tokenExpiresAt: token.expiresAt,
64
+ };
65
+ }
@@ -0,0 +1,16 @@
1
+ export declare const CONFIG_FILE = "sapiom.json";
2
+ export interface SapiomConfig {
3
+ definitionId?: string;
4
+ name?: string;
5
+ host?: string;
6
+ templateId?: string;
7
+ forkId?: string;
8
+ repoFullName?: string;
9
+ defaultBranch?: string;
10
+ }
11
+ export declare function readConfig(dir: string): SapiomConfig | null;
12
+ export type LinkedSapiomConfig = SapiomConfig & {
13
+ definitionId: string;
14
+ };
15
+ export declare function requireConfig(dir: string): LinkedSapiomConfig;
16
+ export declare function writeConfig(dir: string, cfg: SapiomConfig): void;
@@ -0,0 +1,31 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { AgentOperationError } from './errors.js';
4
+ export const CONFIG_FILE = 'sapiom.json';
5
+ export function readConfig(dir) {
6
+ const file = path.join(dir, CONFIG_FILE);
7
+ if (!existsSync(file))
8
+ return null;
9
+ try {
10
+ return JSON.parse(readFileSync(file, 'utf8'));
11
+ }
12
+ catch {
13
+ throw new AgentOperationError({ code: 'BAD_CONFIG', message: `${CONFIG_FILE} is not valid JSON.` });
14
+ }
15
+ }
16
+ export function requireConfig(dir) {
17
+ const cfg = readConfig(dir);
18
+ if (!cfg?.definitionId) {
19
+ throw new AgentOperationError({
20
+ code: 'NOT_LINKED',
21
+ message: 'This project is not linked to a Sapiom agent.',
22
+ hint: 'Run: sapiom agents link <name>',
23
+ });
24
+ }
25
+ return cfg;
26
+ }
27
+ export function writeConfig(dir, cfg) {
28
+ const existing = readConfig(dir) ?? {};
29
+ const merged = { ...existing, ...cfg };
30
+ writeFileSync(path.join(dir, CONFIG_FILE), JSON.stringify(merged, null, 2) + '\n');
31
+ }
@@ -0,0 +1,4 @@
1
+ import type { CostNode, ExecutionProjection, ExecutionRef } from "./types.js";
2
+ export declare function decodeCostNode(raw: unknown): CostNode | null;
3
+ export declare function decodeExecutionRef(raw: unknown): ExecutionRef;
4
+ export declare function decodeExecutionProjection(raw: unknown): ExecutionProjection;
@@ -0,0 +1,167 @@
1
+ function isRecord(v) {
2
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3
+ }
4
+ function rec(v) {
5
+ return isRecord(v) ? v : {};
6
+ }
7
+ function str(fallback, ...candidates) {
8
+ for (const c of candidates)
9
+ if (typeof c === "string")
10
+ return c;
11
+ return fallback;
12
+ }
13
+ function strOrNull(...candidates) {
14
+ for (const c of candidates)
15
+ if (typeof c === "string")
16
+ return c;
17
+ return null;
18
+ }
19
+ function numOr(fallback, v) {
20
+ return typeof v === "number" && Number.isFinite(v) ? v : fallback;
21
+ }
22
+ function recordOrNull(v) {
23
+ return isRecord(v) ? v : null;
24
+ }
25
+ const SETTLE_STATES = ["pending", "settling", "final"];
26
+ function settleState(v) {
27
+ return typeof v === "string" && SETTLE_STATES.includes(v)
28
+ ? v
29
+ : "final";
30
+ }
31
+ export function decodeCostNode(raw) {
32
+ if (!isRecord(raw))
33
+ return null;
34
+ return {
35
+ authorizedUsd: str("0", raw.authorizedUsd),
36
+ capturedUsd: str("0", raw.capturedUsd),
37
+ settleState: settleState(raw.settleState),
38
+ };
39
+ }
40
+ function decodeStepErrorFrame(raw) {
41
+ const r = rec(raw);
42
+ const frame = {};
43
+ if (typeof r.function === "string")
44
+ frame.function = r.function;
45
+ if (typeof r.file === "string")
46
+ frame.file = r.file;
47
+ if (typeof r.line === "number")
48
+ frame.line = r.line;
49
+ if (typeof r.column === "number")
50
+ frame.column = r.column;
51
+ return frame;
52
+ }
53
+ function decodeStepErrorTrace(raw) {
54
+ if (typeof raw === "string") {
55
+ return { frames: [], sourceMapped: false, raw };
56
+ }
57
+ if (!isRecord(raw))
58
+ return null;
59
+ const trace = {
60
+ frames: Array.isArray(raw.frames) ? raw.frames.map(decodeStepErrorFrame) : [],
61
+ sourceMapped: raw.sourceMapped === true,
62
+ };
63
+ if (typeof raw.raw === "string")
64
+ trace.raw = raw.raw;
65
+ return trace;
66
+ }
67
+ function decodeStepError(raw) {
68
+ if (!isRecord(raw))
69
+ return null;
70
+ const message = strOrNull(raw.message);
71
+ if (message === null)
72
+ return null;
73
+ return {
74
+ message,
75
+ trace: decodeStepErrorTrace(raw.trace ?? raw.stack ?? null),
76
+ traceUnavailableReason: strOrNull(raw.traceUnavailableReason),
77
+ };
78
+ }
79
+ function decodeStepEvent(raw) {
80
+ const r = rec(raw);
81
+ return {
82
+ sourceId: str("", r.sourceId),
83
+ sequence: numOr(0, r.sequence),
84
+ kind: str("", r.kind),
85
+ payload: recordOrNull(r.payload) ?? {},
86
+ eventTs: strOrNull(r.eventTs),
87
+ };
88
+ }
89
+ function decodeDispatchRef(raw) {
90
+ if (!isRecord(raw))
91
+ return null;
92
+ return {
93
+ childExecutionId: str("", raw.childExecutionId, raw.targetId, raw.executionId),
94
+ targetType: str("", raw.targetType),
95
+ correlationId: str("", raw.correlationId),
96
+ status: str("", raw.status),
97
+ };
98
+ }
99
+ export function decodeExecutionRef(raw) {
100
+ const r = rec(raw);
101
+ const executionId = str("", r.executionId, r.id);
102
+ return {
103
+ executionId,
104
+ traceRoot: str(executionId, r.traceRoot, r.rootExecutionId),
105
+ name: str("", r.name),
106
+ status: str("", r.status),
107
+ };
108
+ }
109
+ function decodeStep(raw) {
110
+ const r = rec(raw);
111
+ return {
112
+ stepName: str("", r.stepName),
113
+ stepOrder: numOr(0, r.stepOrder),
114
+ attempt: numOr(0, r.attempt),
115
+ status: str("", r.status),
116
+ spanId: strOrNull(r.spanId),
117
+ startedAt: strOrNull(r.startedAt),
118
+ finishedAt: strOrNull(r.finishedAt),
119
+ input: r.input ?? null,
120
+ output: r.output ?? null,
121
+ sharedStateAfter: recordOrNull(r.sharedStateAfter),
122
+ nextDirective: r.nextDirective ?? null,
123
+ cost: decodeCostNode(r.cost),
124
+ logs: r.logs ?? null,
125
+ events: Array.isArray(r.events) ? r.events.map(decodeStepEvent) : [],
126
+ error: decodeStepError(r.error),
127
+ dispatch: decodeDispatchRef(r.dispatch),
128
+ };
129
+ }
130
+ export function decodeExecutionProjection(raw) {
131
+ const r = rec(raw);
132
+ const id = str("", r.id, r.executionId);
133
+ const traceRoot = str(id, r.traceRoot, r.rootExecutionId);
134
+ const traceParent = strOrNull(r.traceParent, r.parentExecutionId);
135
+ return {
136
+ id,
137
+ name: str("", r.name),
138
+ organizationId: strOrNull(r.organizationId),
139
+ tenantId: strOrNull(r.tenantId),
140
+ status: str("", r.status),
141
+ currentStep: strOrNull(r.currentStep),
142
+ currentStepAttempt: numOr(0, r.currentStepAttempt),
143
+ version: numOr(0, r.version),
144
+ definitionId: strOrNull(r.definitionId),
145
+ buildRunId: strOrNull(r.buildRunId),
146
+ idempotencyKey: strOrNull(r.idempotencyKey),
147
+ pausedSignalName: strOrNull(r.pausedSignalName),
148
+ pausedSignalCorrelationId: strOrNull(r.pausedSignalCorrelationId),
149
+ pausedUntil: strOrNull(r.pausedUntil),
150
+ startedAt: str("", r.startedAt),
151
+ finishedAt: strOrNull(r.finishedAt),
152
+ input: r.input ?? null,
153
+ sharedState: recordOrNull(r.sharedState) ?? {},
154
+ output: r.output ?? null,
155
+ error: r.error ?? null,
156
+ pausedStepInputSchema: recordOrNull(r.pausedStepInputSchema),
157
+ pausedStepInputExample: r.pausedStepInputExample ?? null,
158
+ traceRoot,
159
+ rootExecutionId: str(traceRoot, r.rootExecutionId, r.traceRoot),
160
+ traceParent,
161
+ parentExecutionId: strOrNull(r.parentExecutionId, r.traceParent),
162
+ traceId: strOrNull(r.traceId),
163
+ children: Array.isArray(r.children) ? r.children.map(decodeExecutionRef) : [],
164
+ cost: decodeCostNode(r.cost),
165
+ steps: Array.isArray(r.steps) ? r.steps.map(decodeStep) : [],
166
+ };
167
+ }
@@ -0,0 +1,12 @@
1
+ import { GatewayClient } from './client.js';
2
+ export interface DeployOptions {
3
+ projectDir: string;
4
+ definitionId: string;
5
+ branch?: string;
6
+ }
7
+ export interface DeployResult {
8
+ definitionId: string;
9
+ buildRunId: string;
10
+ status: string;
11
+ }
12
+ export declare function deploy(opts: DeployOptions, client: GatewayClient): Promise<DeployResult>;
@@ -0,0 +1,61 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ import { bundleForDeploy } from './bundle.js';
5
+ import { AgentOperationError } from './errors.js';
6
+ import { assertDeployable, pushSynthesizedTree } from './git.js';
7
+ const TERMINAL = new Set(['ready', 'failed', 'cancelled', 'superseded']);
8
+ const POLL_DELAYS_MS = [1000, 2000, 3000, 5000, 5000, 8000, 10000];
9
+ const POLL_BUDGET_MS = 300000;
10
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
11
+ export async function deploy(opts, client) {
12
+ const { projectDir, definitionId, branch = 'main' } = opts;
13
+ assertDeployable(projectDir);
14
+ const { code, dependencies } = await bundleForDeploy(projectDir);
15
+ const { pushUrl } = await client.post(`/definitions/${definitionId}/push-credentials`, {});
16
+ const treeDir = mkdtempSync(path.join(tmpdir(), 'sapiom-deploy-'));
17
+ try {
18
+ writeFileSync(path.join(treeDir, 'index.ts'), code);
19
+ writeFileSync(path.join(treeDir, 'package.json'), JSON.stringify({ name: 'agent-definition', private: true, type: 'module', dependencies }, null, 2) + '\n');
20
+ pushSynthesizedTree(treeDir, pushUrl, branch);
21
+ }
22
+ finally {
23
+ rmSync(treeDir, { recursive: true, force: true });
24
+ }
25
+ const triggered = await client.post(`/definitions/${definitionId}/builds`, {});
26
+ const buildRunId = triggered.buildRunId ?? triggered.id;
27
+ if (!buildRunId) {
28
+ throw new AgentOperationError({
29
+ code: 'BUILD_NO_ID',
30
+ message: 'The build was triggered but no build id was returned.',
31
+ });
32
+ }
33
+ const final = await pollBuild(client, definitionId, buildRunId);
34
+ if (final.status !== 'ready') {
35
+ throw new AgentOperationError({
36
+ code: 'BUILD_FAILED',
37
+ message: `Build ${final.status}.`,
38
+ step: 'build',
39
+ hint: final.error?.stack || final.error?.message,
40
+ });
41
+ }
42
+ return { definitionId, buildRunId, status: final.status };
43
+ }
44
+ async function pollBuild(client, definitionId, buildRunId) {
45
+ let elapsed = 0;
46
+ let i = 0;
47
+ while (elapsed < POLL_BUDGET_MS) {
48
+ const build = await client.get(`/definitions/${definitionId}/builds/${buildRunId}`);
49
+ if (TERMINAL.has(build.status))
50
+ return build;
51
+ const delay = POLL_DELAYS_MS[Math.min(i++, POLL_DELAYS_MS.length - 1)];
52
+ await sleep(delay);
53
+ elapsed += delay;
54
+ }
55
+ throw new AgentOperationError({
56
+ code: 'BUILD_TIMEOUT',
57
+ message: 'Build did not finish in time.',
58
+ step: 'build',
59
+ hint: `Check it later via the logs API for build ${buildRunId}`,
60
+ });
61
+ }
@@ -0,0 +1,15 @@
1
+ export interface StructuredError {
2
+ code: string;
3
+ message: string;
4
+ step?: string;
5
+ hint?: string;
6
+ docsUrl?: string;
7
+ }
8
+ export declare class AgentOperationError extends Error {
9
+ readonly code: string;
10
+ readonly step?: string;
11
+ readonly hint?: string;
12
+ readonly docsUrl?: string;
13
+ constructor(err: StructuredError);
14
+ toStructured(): StructuredError;
15
+ }
@@ -0,0 +1,19 @@
1
+ export class AgentOperationError extends Error {
2
+ constructor(err) {
3
+ super(err.message);
4
+ this.name = 'AgentOperationError';
5
+ this.code = err.code;
6
+ this.step = err.step;
7
+ this.hint = err.hint;
8
+ this.docsUrl = err.docsUrl;
9
+ }
10
+ toStructured() {
11
+ return {
12
+ code: this.code,
13
+ message: this.message,
14
+ ...(this.step ? { step: this.step } : {}),
15
+ ...(this.hint ? { hint: this.hint } : {}),
16
+ ...(this.docsUrl ? { docsUrl: this.docsUrl } : {}),
17
+ };
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ export declare function assertDeployable(dir: string): void;
2
+ export declare function redactCredentials(text: string): string;
3
+ export interface CloneRepoOptions {
4
+ cloneUrl: string;
5
+ targetDir: string;
6
+ branch: string;
7
+ repoFullName: string;
8
+ cwd: string;
9
+ }
10
+ export declare function cloneRepo(opts: CloneRepoOptions): void;
11
+ export declare function pushHead(dir: string, pushUrl: string, branch: string): void;
12
+ export declare function pushSynthesizedTree(treeDir: string, pushUrl: string, branch: string): void;