@contentful/experience-design-system-generation 2.36.2-dev-build-a6f80ae6.0 → 2.37.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 (65) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/agent-names.d.ts +1 -0
  3. package/dist/src/agent-names.js +7 -0
  4. package/dist/src/controller/create-generation-endpoint.d.ts +3 -0
  5. package/dist/src/controller/create-generation-endpoint.js +15 -0
  6. package/dist/src/controller/helpers/validate-generation-request.d.ts +2 -0
  7. package/dist/src/controller/helpers/validate-generation-request.js +18 -0
  8. package/dist/src/index.d.ts +19 -7
  9. package/dist/src/index.js +18 -6
  10. package/dist/src/orchestrator/execute-generation-orchestrator.d.ts +6 -0
  11. package/dist/src/orchestrator/execute-generation-orchestrator.js +12 -0
  12. package/dist/src/services/agent/helpers/build-agent-args.d.ts +6 -0
  13. package/dist/src/services/agent/helpers/build-agent-args.js +58 -0
  14. package/dist/src/services/agent/helpers/check-agent-auth.d.ts +3 -0
  15. package/dist/src/services/agent/helpers/check-agent-auth.js +19 -0
  16. package/dist/src/services/agent/helpers/check-claude-auth.d.ts +2 -0
  17. package/dist/src/services/agent/helpers/check-claude-auth.js +37 -0
  18. package/dist/src/services/agent/helpers/describe-agent-failure.d.ts +2 -0
  19. package/dist/src/services/agent/helpers/describe-agent-failure.js +5 -0
  20. package/dist/src/services/agent/helpers/extract-sentinel-output.d.ts +1 -0
  21. package/dist/src/services/agent/helpers/extract-sentinel-output.js +16 -0
  22. package/dist/src/services/agent/helpers/resolve-agent-binary.d.ts +2 -0
  23. package/dist/src/services/agent/helpers/resolve-agent-binary.js +8 -0
  24. package/dist/src/services/agent/local-cli-agent-invoker.d.ts +3 -0
  25. package/dist/src/{agent-invoker.js → services/agent/local-cli-agent-invoker.js} +2 -2
  26. package/dist/src/services/agent/run-agent.d.ts +15 -0
  27. package/dist/src/services/agent/run-agent.js +70 -0
  28. package/dist/src/services/parsing/helpers/read-tool-call-objects.d.ts +4 -0
  29. package/dist/src/services/parsing/helpers/read-tool-call-objects.js +54 -0
  30. package/dist/src/services/parsing/parse-map-token-tool-calls.d.ts +2 -0
  31. package/dist/src/services/parsing/parse-map-token-tool-calls.js +39 -0
  32. package/dist/src/services/parsing/parse-prop-tool-calls.d.ts +2 -0
  33. package/dist/src/services/parsing/parse-prop-tool-calls.js +91 -0
  34. package/dist/src/services/parsing/parse-select-tool-calls.d.ts +2 -0
  35. package/dist/src/services/parsing/parse-select-tool-calls.js +27 -0
  36. package/dist/src/services/parsing/parse-token-tool-calls.d.ts +2 -0
  37. package/dist/src/services/parsing/parse-token-tool-calls.js +39 -0
  38. package/dist/src/services/prompts/build-prompt.d.ts +4 -0
  39. package/dist/src/services/prompts/build-prompt.js +8 -0
  40. package/dist/src/services/prompts/helpers/build-preamble.d.ts +2 -0
  41. package/dist/src/services/prompts/helpers/build-preamble.js +48 -0
  42. package/dist/src/services/prompts/helpers/build-skill-preambles.d.ts +4 -0
  43. package/dist/src/services/prompts/helpers/build-skill-preambles.js +126 -0
  44. package/dist/src/services/prompts/helpers/format-source-refs.d.ts +2 -0
  45. package/dist/src/services/prompts/helpers/format-source-refs.js +31 -0
  46. package/dist/src/services/prompts/helpers/format-token-candidates.d.ts +8 -0
  47. package/dist/src/services/prompts/helpers/format-token-candidates.js +80 -0
  48. package/dist/src/services/prompts/helpers/resolve-skill-file.d.ts +4 -0
  49. package/dist/src/services/prompts/helpers/resolve-skill-file.js +49 -0
  50. package/dist/src/types/agent.d.ts +10 -0
  51. package/dist/src/types/agent.js +1 -0
  52. package/dist/src/types/contract.d.ts +24 -0
  53. package/dist/src/types/contract.js +1 -0
  54. package/dist/src/types/invoker.d.ts +17 -0
  55. package/dist/src/types/invoker.js +1 -0
  56. package/dist/src/types/prompt.d.ts +53 -0
  57. package/dist/src/types/prompt.js +1 -0
  58. package/dist/src/types/tool-calls.d.ts +91 -0
  59. package/dist/src/types/tool-calls.js +1 -0
  60. package/package.json +2 -2
  61. package/dist/src/agent-invoker.d.ts +0 -25
  62. package/dist/src/agent-runner.d.ts +0 -144
  63. package/dist/src/agent-runner.js +0 -564
  64. package/dist/src/prompt-builder.d.ts +0 -81
  65. package/dist/src/prompt-builder.js +0 -344
@@ -1,564 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- export { AGENT_NAMES, DEFAULT_AGENT_NAME, isAgentName } from './agent-names.js';
3
- const VALID_SELECT_TOOL_NAMES = new Set(['select_component', 'reject_component']);
4
- function findJsonObjectEnd(line) {
5
- let depth = 0;
6
- let inString = false;
7
- let escaped = false;
8
- for (let i = 0; i < line.length; i++) {
9
- const ch = line[i];
10
- if (escaped) {
11
- escaped = false;
12
- }
13
- else if (ch === '\\' && inString) {
14
- escaped = true;
15
- }
16
- else if (ch === '"') {
17
- inString = !inString;
18
- }
19
- else if (!inString && ch === '{') {
20
- depth++;
21
- }
22
- else if (!inString && ch === '}' && --depth === 0) {
23
- return i;
24
- }
25
- }
26
- return -1;
27
- }
28
- /**
29
- * Reads an agent's stdout into the tool-call objects it carries. Lines that do
30
- * not start with `{` are the agent's prose and are skipped silently, as before.
31
- */
32
- function readToolCallObjects(stdout) {
33
- const objects = [];
34
- const warnings = [];
35
- for (const raw of stdout.split('\n')) {
36
- const line = raw.trim();
37
- if (!line.startsWith('{'))
38
- continue;
39
- const end = findJsonObjectEnd(line);
40
- if (end === -1) {
41
- warnings.push(`unparseable line: ${line.slice(0, 120)}`);
42
- continue;
43
- }
44
- let parsed;
45
- try {
46
- parsed = JSON.parse(line.slice(0, end + 1));
47
- }
48
- catch {
49
- warnings.push(`unparseable line: ${line.slice(0, 120)}`);
50
- continue;
51
- }
52
- const trailing = line.slice(end + 1);
53
- if (trailing.trim()) {
54
- warnings.push(`ignored trailing content after JSON: ${trailing.trim().slice(0, 120)}`);
55
- }
56
- if (typeof parsed === 'object' && parsed !== null && 'tool' in parsed) {
57
- objects.push(parsed);
58
- }
59
- }
60
- return { objects, warnings };
61
- }
62
- export function parseSelectToolCallLines(stdout) {
63
- const calls = [];
64
- const { objects, warnings } = readToolCallObjects(stdout);
65
- for (const rec of objects) {
66
- if (!VALID_SELECT_TOOL_NAMES.has(rec.tool))
67
- continue;
68
- if (typeof rec.name !== 'string' || !rec.name) {
69
- warnings.push(`${String(rec.tool)} missing name — skipped`);
70
- continue;
71
- }
72
- const call = {
73
- tool: rec.tool,
74
- name: rec.name,
75
- };
76
- if (typeof rec.reason === 'string')
77
- call.reason = rec.reason;
78
- if (typeof rec.confidence === 'number' && rec.confidence >= 1 && rec.confidence <= 5) {
79
- if (call.tool === 'select_component') {
80
- call.confidence = rec.confidence;
81
- }
82
- else {
83
- call.confidence = rec.confidence;
84
- }
85
- }
86
- calls.push(call);
87
- }
88
- return { calls, warnings };
89
- }
90
- const VALID_TOOL_NAMES = new Set(['classify_prop', 'exclude_prop', 'classify_component', 'classify_slot']);
91
- const VALID_TOKEN_TOOL_NAMES = new Set(['set_token', 'set_group']);
92
- const VALID_CDF_TYPES = new Set(['string', 'richtext', 'media', 'enum', 'token', 'boolean']);
93
- const VALID_CATEGORIES = new Set(['content', 'design', 'state']);
94
- export function parseToolCallLines(stdout) {
95
- const calls = [];
96
- const { objects, warnings } = readToolCallObjects(stdout);
97
- for (const rec of objects) {
98
- if (!VALID_TOOL_NAMES.has(rec.tool)) {
99
- warnings.push(`unknown tool: ${String(rec.tool)}`);
100
- continue;
101
- }
102
- const tool = rec.tool;
103
- if (tool === 'classify_prop') {
104
- if (typeof rec.prop !== 'string' || !rec.prop) {
105
- warnings.push('classify_prop missing prop name — skipped');
106
- continue;
107
- }
108
- if (typeof rec.cdf_type !== 'string' || !VALID_CDF_TYPES.has(rec.cdf_type)) {
109
- warnings.push(`classify_prop '${rec.prop}': invalid cdf_type '${String(rec.cdf_type)}' — skipped`);
110
- continue;
111
- }
112
- if (typeof rec.cdf_category !== 'string' || !VALID_CATEGORIES.has(rec.cdf_category)) {
113
- warnings.push(`classify_prop '${rec.prop}': invalid cdf_category '${String(rec.cdf_category)}' — skipped`);
114
- continue;
115
- }
116
- const call = {
117
- tool: 'classify_prop',
118
- prop: rec.prop,
119
- cdf_type: rec.cdf_type,
120
- cdf_category: rec.cdf_category,
121
- };
122
- if (Array.isArray(rec.values) && rec.values.every((v) => typeof v === 'string')) {
123
- call.values = rec.values;
124
- }
125
- if (typeof rec.token_kind === 'string')
126
- call.token_kind = rec.token_kind;
127
- if (typeof rec.required === 'boolean')
128
- call.required = rec.required;
129
- if (typeof rec.description === 'string')
130
- call.description = rec.description;
131
- if (typeof rec.default === 'string' || typeof rec.default === 'boolean')
132
- call.default = rec.default;
133
- if (typeof rec.reason === 'string')
134
- call.reason = rec.reason;
135
- calls.push(call);
136
- }
137
- else if (tool === 'exclude_prop') {
138
- if (typeof rec.prop !== 'string' || !rec.prop) {
139
- warnings.push('exclude_prop missing prop name — skipped');
140
- continue;
141
- }
142
- calls.push({
143
- tool: 'exclude_prop',
144
- prop: rec.prop,
145
- reason: typeof rec.reason === 'string' ? rec.reason : '',
146
- });
147
- }
148
- else if (tool === 'classify_component') {
149
- const call = { tool: 'classify_component' };
150
- if (typeof rec.description === 'string')
151
- call.description = rec.description;
152
- if (typeof rec.rationale === 'object' && rec.rationale !== null) {
153
- const r = rec.rationale;
154
- const rationale = {};
155
- if (typeof r.description === 'string')
156
- rationale.description = r.description;
157
- if (typeof r.props === 'string')
158
- rationale.props = r.props;
159
- if (typeof r.slots === 'string')
160
- rationale.slots = r.slots;
161
- if (Object.keys(rationale).length > 0)
162
- call.rationale = rationale;
163
- }
164
- calls.push(call);
165
- }
166
- else if (tool === 'classify_slot') {
167
- if (typeof rec.slot !== 'string' || !rec.slot) {
168
- warnings.push('classify_slot missing slot name — skipped');
169
- continue;
170
- }
171
- const call = { tool: 'classify_slot', slot: rec.slot };
172
- if (typeof rec.required === 'boolean')
173
- call.required = rec.required;
174
- if (Array.isArray(rec.allowed_components) && rec.allowed_components.every((v) => typeof v === 'string')) {
175
- call.allowed_components = rec.allowed_components;
176
- }
177
- if (typeof rec.description === 'string')
178
- call.description = rec.description;
179
- if (typeof rec.rationale === 'string')
180
- call.rationale = rec.rationale;
181
- calls.push(call);
182
- }
183
- }
184
- return { calls, warnings };
185
- }
186
- export function parseTokenToolCallLines(stdout) {
187
- const calls = [];
188
- const { objects, warnings } = readToolCallObjects(stdout);
189
- for (const rec of objects) {
190
- if (!VALID_TOKEN_TOOL_NAMES.has(rec.tool))
191
- continue; // not a token call — skip silently
192
- if (rec.tool === 'set_token') {
193
- if (typeof rec.path !== 'string' || !rec.path) {
194
- warnings.push('set_token missing path — skipped');
195
- continue;
196
- }
197
- if (typeof rec.type !== 'string' || !rec.type) {
198
- warnings.push(`set_token '${rec.path}': missing type — skipped`);
199
- continue;
200
- }
201
- if (!('value' in rec)) {
202
- warnings.push(`set_token '${rec.path}': missing value — skipped`);
203
- continue;
204
- }
205
- const call = { tool: 'set_token', path: rec.path, type: rec.type, value: rec.value };
206
- if (typeof rec.description === 'string')
207
- call.description = rec.description;
208
- calls.push(call);
209
- }
210
- else if (rec.tool === 'set_group') {
211
- if (typeof rec.path !== 'string' || !rec.path) {
212
- warnings.push('set_group missing path — skipped');
213
- continue;
214
- }
215
- const call = { tool: 'set_group', path: rec.path };
216
- if (typeof rec.description === 'string')
217
- call.description = rec.description;
218
- calls.push(call);
219
- }
220
- }
221
- return { calls, warnings };
222
- }
223
- function isStringArray(value) {
224
- return Array.isArray(value) && value.every((v) => typeof v === 'string');
225
- }
226
- export function parseMapTokenPropToolCallLines(stdout) {
227
- const calls = [];
228
- const warnings = [];
229
- for (const raw of stdout.split('\n')) {
230
- const line = raw.trim();
231
- if (!line.startsWith('{'))
232
- continue;
233
- let obj;
234
- try {
235
- obj = JSON.parse(line);
236
- }
237
- catch {
238
- warnings.push(`unparseable line: ${line.slice(0, 120)}`);
239
- continue;
240
- }
241
- if (typeof obj !== 'object' || obj === null || !('tool' in obj))
242
- continue;
243
- const rec = obj;
244
- if (rec.tool !== 'map_token_prop')
245
- continue; // not a map-tokens call — skip silently
246
- if (typeof rec.component !== 'string' || !rec.component) {
247
- warnings.push('map_token_prop missing component — skipped');
248
- continue;
249
- }
250
- if (typeof rec.prop !== 'string' || !rec.prop) {
251
- warnings.push(`map_token_prop '${rec.component}': missing prop — skipped`);
252
- continue;
253
- }
254
- if (!isStringArray(rec.token_allowed) || rec.token_allowed.length === 0) {
255
- warnings.push(`map_token_prop '${rec.component}.${String(rec.prop)}': missing or empty token_allowed — skipped`);
256
- continue;
257
- }
258
- const call = {
259
- tool: 'map_token_prop',
260
- component: rec.component,
261
- prop: rec.prop,
262
- token_allowed: rec.token_allowed,
263
- };
264
- calls.push(call);
265
- }
266
- return { calls, warnings };
267
- }
268
- // --- Agent invocation ---
269
- const AGENT_BINARIES = {
270
- claude: 'claude',
271
- codex: 'codex',
272
- opencode: 'opencode',
273
- cursor: 'cursor-agent',
274
- copilot: 'copilot',
275
- };
276
- export function resolveBinary(agent) {
277
- const envKey = `EDS_AGENT_BINARY_${agent.toUpperCase()}`;
278
- const override = process.env[envKey];
279
- if (override && override.trim())
280
- return override.trim();
281
- return AGENT_BINARIES[agent];
282
- }
283
- /** Per-agent env vars that switch model calls to AWS Bedrock. */
284
- const BEDROCK_ENV_BY_AGENT = {
285
- claude: { CLAUDE_CODE_USE_BEDROCK: '1' },
286
- };
287
- /**
288
- * Agents with a working Bedrock routing mechanism, of any shape (env var,
289
- * argv config override, or a provider-prefixed model string) — not just the
290
- * env-var agents in BEDROCK_ENV_BY_AGENT. cursor is excluded: its own Bedrock
291
- * support is currently broken for non-interactive/CI use.
292
- */
293
- const BEDROCK_CAPABLE_AGENTS = new Set(['claude', 'codex', 'opencode']);
294
- export function agentSupportsBedrock(agent) {
295
- return BEDROCK_CAPABLE_AGENTS.has(agent);
296
- }
297
- const DEFAULT_CODEX_BEDROCK_REGION = 'us-east-1';
298
- const DEFAULT_OPENCODE_MODEL = 'claude-haiku-4-5';
299
- /**
300
- * Default models per agent — lightweight/fast picks to control cost when no
301
- * explicit model is configured. cursor uses `gpt-mini` (verified alias from
302
- * GetUsableModels; haiku is not available in cursor's model catalog).
303
- *
304
- * codex is deliberately absent: with no entry here it gets no `--model` at
305
- * all, so the installed Codex CLI picks whatever its account supports. On
306
- * Bedrock it still needs an explicit id, since the model id there carries an
307
- * `openai.` prefix required by the Bedrock provider and absent from the
308
- * direct api.openai.com id — see DEFAULT_CODEX_BEDROCK_MODEL.
309
- */
310
- const DEFAULT_MODELS = {
311
- claude: 'haiku',
312
- opencode: DEFAULT_OPENCODE_MODEL,
313
- cursor: 'gpt-mini', // cursor alias for gpt-5.4-mini-medium; haiku not in cursor's catalog
314
- copilot: 'Auto', // the only model guaranteed on every Copilot plan (Free/Pro/Business/Enterprise); Pro+ users override via EDS_AGENT_MODEL_COPILOT
315
- };
316
- const DEFAULT_CODEX_BEDROCK_MODEL = 'openai.gpt-5.6-luna';
317
- /**
318
- * Resolve the model for an agent. Explicit flag/creds value wins, then a
319
- * per-agent `EDS_AGENT_MODEL_<AGENT>` env override (mirrors the
320
- * `EDS_AGENT_BINARY_<AGENT>` pattern), otherwise the lightweight default for
321
- * that agent. `bedrock` only affects the fallback default, and only for
322
- * agents whose model id changes shape under Bedrock (codex, opencode) — an
323
- * explicit value or env override is never rewritten.
324
- */
325
- export function resolveAgentModel(agent, explicit, bedrock = false) {
326
- if (explicit && explicit.trim())
327
- return explicit.trim();
328
- const override = process.env[`EDS_AGENT_MODEL_${agent.toUpperCase()}`];
329
- if (override && override.trim())
330
- return override.trim();
331
- if (bedrock && agent === 'codex')
332
- return DEFAULT_CODEX_BEDROCK_MODEL;
333
- if (bedrock && agent === 'opencode')
334
- return withBedrockProviderPrefix(DEFAULT_OPENCODE_MODEL);
335
- return DEFAULT_MODELS[agent];
336
- }
337
- /**
338
- * opencode selects providers via a `provider/model` string rather than a
339
- * flag or env var, so routing it through Bedrock means rewriting the model
340
- * string itself. Left alone if it already carries a provider prefix (an
341
- * explicit --model / EDS_AGENT_MODEL_OPENCODE value naming its own
342
- * provider), since resolveAgentModel only applies this to the bare default.
343
- */
344
- function withBedrockProviderPrefix(model) {
345
- return model.includes('/') ? model : `amazon-bedrock/${model}`;
346
- }
347
- /**
348
- * codex has no Bedrock env var; it requires `-c` config overrides
349
- * (`model_provider` + the Bedrock provider's region) passed as argv, per the
350
- * org's verified codex-bedrock-security-review setup. Region comes from the
351
- * environment's own AWS region var, falling back to us-east-1 — the region
352
- * gpt-5.6-luna's Bedrock endpoint requires — when neither is set.
353
- */
354
- function codexBedrockConfigArgs() {
355
- const region = process.env.AWS_REGION?.trim() || process.env.AWS_DEFAULT_REGION?.trim() || DEFAULT_CODEX_BEDROCK_REGION;
356
- return ['-c', 'model_provider=amazon-bedrock', '-c', `model_providers.amazon-bedrock.region=${region}`];
357
- }
358
- export function buildArgs(agent, prompt, model, promptViaStdin = false, bedrock = false) {
359
- // codex with no configured model resolves to undefined — omit --model
360
- // entirely so the CLI picks its own account-compatible default.
361
- const resolvedModel = resolveAgentModel(agent, model, bedrock);
362
- const modelArg = resolvedModel ? ['--model', resolvedModel] : [];
363
- // When the prompt is delivered on stdin, omit it from argv — a large prompt
364
- // as a command-line argument overflows ARG_MAX (spawn E2BIG). All four CLIs
365
- // read the prompt from stdin when it isn't passed positionally.
366
- const promptArg = promptViaStdin ? [] : [prompt];
367
- switch (agent) {
368
- case 'claude':
369
- return ['--print', ...modelArg, ...promptArg];
370
- case 'codex': {
371
- const bedrockArgs = bedrock ? codexBedrockConfigArgs() : [];
372
- // --dangerously-bypass-approvals-and-sandbox required for non-interactive use
373
- return ['exec', ...bedrockArgs, ...modelArg, '--dangerously-bypass-approvals-and-sandbox', ...promptArg];
374
- }
375
- case 'opencode':
376
- return ['run', ...modelArg, ...promptArg];
377
- case 'cursor':
378
- // cursor-agent uses --print for non-interactive stdout output
379
- return ['--print', ...modelArg, ...promptArg];
380
- case 'copilot': {
381
- // copilot's -p takes the prompt as its value — it MUST come immediately
382
- // after -p or the CLI rejects with "Invalid command format".
383
- // --allow-all-tools mirrors codex's sandbox bypass for non-interactive
384
- // use.
385
- //
386
- // Model handling is special: copilot's UI shows "Auto" as the default
387
- // pick, but the CLI rejects --model Auto ("not available"). Auto is a
388
- // UI-only label — internally, omitting --model IS Auto. So we only
389
- // pass --model when the user set an explicit override (via flag or
390
- // EDS_AGENT_MODEL_COPILOT); the sentinel string 'Auto' means "omit".
391
- //
392
- // Stdin fallback is unsupported: copilot -p requires an inline prompt
393
- // value, so promptViaStdin will fail here — callers must pass the
394
- // prompt inline for copilot.
395
- const copilotModel = resolveAgentModel('copilot', model);
396
- const copilotModelArg = !copilotModel || copilotModel === 'Auto' ? [] : ['--model', copilotModel];
397
- return ['-p', ...promptArg, ...copilotModelArg, '--allow-all-tools'];
398
- }
399
- }
400
- }
401
- export async function runAgent(options) {
402
- const { agent, prompt, timeoutMs, model, onOutput, promptViaStdin, onDebugEvent } = options;
403
- // Fall back to the process-wide EDS_BEDROCK signal (set once by the CLI's
404
- // top-level --bedrock resolution and inherited by every spawned subprocess)
405
- // when a call site doesn't pass `bedrock` explicitly — closes the gap for
406
- // call sites that forget to thread the flag through by hand.
407
- const bedrock = options.bedrock ?? process.env.EDS_BEDROCK === '1';
408
- const binary = resolveBinary(agent);
409
- const useStdin = !!promptViaStdin;
410
- const args = buildArgs(agent, prompt, model, useStdin, bedrock);
411
- const startedAt = Date.now();
412
- onDebugEvent?.('run.start', {
413
- agent,
414
- binary,
415
- model,
416
- bedrock: !!bedrock,
417
- timeoutMs,
418
- promptLen: prompt.length,
419
- promptHead: prompt.slice(0, 500),
420
- });
421
- return new Promise((resolve) => {
422
- const bedrockEnv = bedrock ? BEDROCK_ENV_BY_AGENT[agent] : undefined;
423
- const child = spawn(binary, args, {
424
- stdio: ['pipe', 'pipe', 'pipe'],
425
- ...(bedrockEnv ? { env: { ...process.env, ...bedrockEnv } } : {}),
426
- });
427
- if (useStdin && child.stdin) {
428
- // Guard against EPIPE: the child may close stdin before we finish
429
- // writing (fast exit, or it stops reading). Swallow the write error —
430
- // the child's own exit code/stderr is the source of truth.
431
- child.stdin.on('error', () => { });
432
- child.stdin.write(prompt, () => {
433
- child.stdin?.end();
434
- });
435
- }
436
- else {
437
- child.stdin?.end();
438
- }
439
- let stdout = '';
440
- let stderr = '';
441
- let timedOut = false;
442
- const timer = setTimeout(() => {
443
- timedOut = true;
444
- child.kill('SIGTERM');
445
- }, timeoutMs);
446
- child.stdout?.on('data', (chunk) => {
447
- const text = chunk.toString();
448
- stdout += text;
449
- onOutput?.(text);
450
- });
451
- child.stderr?.on('data', (chunk) => {
452
- stderr += chunk.toString();
453
- });
454
- child.on('close', (code, signal) => {
455
- clearTimeout(timer);
456
- const result = {
457
- exitCode: signal ? 1 : (code ?? 1),
458
- stdout,
459
- stderr,
460
- timedOut,
461
- };
462
- onDebugEvent?.('run.end', {
463
- agent,
464
- model,
465
- durationMs: Date.now() - startedAt,
466
- exitCode: result.exitCode,
467
- signal,
468
- timedOut,
469
- stdoutLen: stdout.length,
470
- stderrLen: stderr.length,
471
- stderrTail: stderr.slice(-1000),
472
- });
473
- resolve(result);
474
- });
475
- });
476
- }
477
- export async function checkAgentAuth(agent) {
478
- const binary = resolveBinary(agent);
479
- // Verify the selected agent's binary exists first — for EVERY agent, not
480
- // just claude. When `binary` is an absolute path (e.g. set via
481
- // EDS_AGENT_BINARY_<AGENT>=/opt/custom/bin), `which` on some shells doesn't
482
- // resolve it — check the filesystem directly for absolute paths, and fall
483
- // back to `which` for bare names on $PATH.
484
- const binaryExists = await new Promise((resolve) => {
485
- if (binary.startsWith('/')) {
486
- import('node:fs/promises').then((fs) => fs.access(binary).then(() => resolve(true), () => resolve(false)));
487
- return;
488
- }
489
- const child = spawn(process.platform === 'win32' ? 'where' : 'which', [binary], { stdio: 'ignore' });
490
- child.on('close', (code) => resolve(code === 0));
491
- });
492
- if (!binaryExists)
493
- return 'not-found';
494
- // Only Claude exposes `auth status --json`. Non-Claude agents are considered
495
- // authenticated once their binary is present — never gate them on claude
496
- // (e.g. Codex-via-Bedrock users would otherwise be blocked by a claude check).
497
- if (agent !== 'claude')
498
- return 'ok';
499
- // Use `claude auth status` — fast, no API call, works regardless of which
500
- // auth provider (direct, Bedrock, Vertex) or whether AWS_PROFILE is set.
501
- return new Promise((resolve) => {
502
- const child = spawn(binary, ['auth', 'status', '--json'], {
503
- stdio: ['ignore', 'pipe', 'pipe'],
504
- });
505
- let stdout = '';
506
- let done = false;
507
- const timer = setTimeout(() => {
508
- if (!done) {
509
- done = true;
510
- child.kill('SIGTERM');
511
- resolve('unauthenticated');
512
- }
513
- }, 5000);
514
- child.stdout?.on('data', (chunk) => {
515
- stdout += chunk.toString();
516
- });
517
- child.on('close', (code) => {
518
- if (done)
519
- return;
520
- done = true;
521
- clearTimeout(timer);
522
- if (code !== 0) {
523
- resolve('unauthenticated');
524
- return;
525
- }
526
- try {
527
- const status = JSON.parse(stdout);
528
- resolve(status.loggedIn ? 'ok' : 'unauthenticated');
529
- }
530
- catch {
531
- resolve('unauthenticated');
532
- }
533
- });
534
- });
535
- }
536
- /**
537
- * Build a diagnostic string from a failed agent run, surfacing the agent's own
538
- * stderr (or stdout, when stderr is empty) so callers never emit a context-free
539
- * "agent failed". Callers only invoke this on a failed run: either a non-zero
540
- * exit, or a zero exit that produced no tool calls. A non-zero exit is reported
541
- * as such; a zero exit is therefore the "produced no tool calls" case.
542
- */
543
- export function describeAgentFailure(result, maxDetail = 800) {
544
- const base = result.exitCode !== 0 ? `agent exited with code ${result.exitCode}` : 'agent produced no tool calls';
545
- const detail = (result.stderr.trim() || result.stdout.trim()).slice(-maxDetail).trim();
546
- return detail ? `${base} — ${detail}` : base;
547
- }
548
- export function extractSentinelOutput(stdout) {
549
- const START = '<<<EDS_OUTPUT_START>>>';
550
- const END = '<<<EDS_OUTPUT_END>>>';
551
- const startIdx = stdout.indexOf(START);
552
- const endIdx = stdout.indexOf(END);
553
- if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
554
- return null;
555
- // Check for multiple blocks
556
- const secondStart = stdout.indexOf(START, startIdx + START.length);
557
- if (secondStart !== -1 && secondStart < endIdx)
558
- return 'multiple';
559
- const afterStart = stdout.indexOf(END, startIdx);
560
- const secondEnd = stdout.indexOf(END, afterStart + END.length);
561
- if (secondEnd !== -1)
562
- return 'multiple';
563
- return stdout.slice(startIdx + START.length, endIdx).trim();
564
- }
@@ -1,81 +0,0 @@
1
- import { type CDFComponentEntry } from '@contentful/experience-design-system-types';
2
- /** `components` — classify component props; `tokens` — classify design tokens; `select` — decide whether a component belongs in Contentful Experience Orchestration; `map-tokens` — suggest `$token.allowed` restrictions for design-category token props */
3
- export type Skill = 'components' | 'tokens' | 'select' | 'map-tokens';
4
- export type Mode = 'autonomous';
5
- /**
6
- * A component name paired with the source file it was extracted from, for
7
- * token-mapping evidence. `content` is the real file text (bounded, see
8
- * MAX_COMPONENT_SOURCE_CHARS) — this pipeline is agent-fs-free by design (see
9
- * generate-components.md's "you do not write any files"), so the caller must
10
- * read the file itself and inline the text here rather than handing the
11
- * agent a path and expecting it to open the file. `null` when the file
12
- * couldn't be read (moved/deleted since extraction) — callers fall back to
13
- * inferring from the prop name and $token.kind alone in that case.
14
- * `siblingFiles` carries the content of files the source file relatively
15
- * imports (e.g. a co-located `.styles.ts`) — token-resolution logic often
16
- * lives one hop away from the component file itself. `truncatedSiblingCount`
17
- * is set when the caller found more resolvable sibling files than it inlines
18
- * (see MAX_SIBLING_FILES) — surfaced in the prompt so the classifier knows
19
- * evidence was dropped rather than that the search came up empty.
20
- */
21
- export interface ComponentSourceRef {
22
- component: string;
23
- sourcePath: string;
24
- content: string | null;
25
- siblingFiles?: Array<{
26
- path: string;
27
- content: string;
28
- }>;
29
- truncatedSiblingCount?: number;
30
- /**
31
- * Properties with at least one use that fell outside the excerpt budget of
32
- * the file it sits in. The reader cannot see that use, so its absence is a
33
- * gap in the evidence, not evidence of absence.
34
- */
35
- usesNotShown?: string[];
36
- }
37
- /** Plain-data shape of the CDF generated so far — component name -> component entry. */
38
- export type GeneratedCdf = Record<string, CDFComponentEntry>;
39
- /**
40
- * Render the warning banner shown when a custom skill prompt is active.
41
- * Always cites the bundled invariants that the override bypasses so the
42
- * operator cannot miss it.
43
- */
44
- export declare function formatCustomPromptBanner(skill: 'components' | 'select', path: string): string;
45
- export interface PromptOptions {
46
- skill: Skill;
47
- mode: Mode;
48
- rawComponentsInline?: string;
49
- rawTokensInline?: string;
50
- /** Original filename for raw tokens — used to set the correct code fence language. */
51
- rawTokensFilename?: string;
52
- tokensInline?: string;
53
- tokenMapInline?: string;
54
- outDir: string;
55
- /** For components skill only: the single component's name (used in error messages). */
56
- componentName?: string;
57
- /** For map-tokens skill: the CDF generated so far. Filtered internally to design-category token-typed props only. */
58
- generatedCdf?: GeneratedCdf;
59
- /** For map-tokens skill: the full DTCG token tree. Flattened internally to a path+`$type` index, with `$value` stripped. */
60
- tokenTree?: Record<string, unknown>;
61
- /** Component source file references — consumption evidence for the components skill, explicit-restriction evidence (comments, allowlists) for map-tokens. */
62
- componentSourceRefs?: ComponentSourceRef[];
63
- /**
64
- * Feature 8: custom prompt path override. When set, this absolute or relative
65
- * `.md` path is read in place of the bundled skill file. The bundled-prompt
66
- * invariants (utility-wrapper rejection, description content rules, etc.) do
67
- * NOT apply under an override — callers are responsible for showing the
68
- * appropriate warning banner.
69
- */
70
- skillPathOverride?: string;
71
- /** Inline prompt instructions, taking precedence over the bundled skill or path override. */
72
- skillContentOverride?: string;
73
- /** JSON-serialized summary of existing space Components. Callers pre-project the shape per skill. */
74
- existingComponentsInline?: string;
75
- /** JSON-serialized summary of existing space DesignTokens. Callers pre-project the shape per skill. */
76
- existingTokensInline?: string;
77
- /** JSON-serialized hard allowlist for generated slot allowed-components references. */
78
- componentAllowlistInline?: string;
79
- }
80
- export declare function buildPrompt(options: PromptOptions): Promise<string>;
81
- export declare function resolveSkillPath(skill: Skill): string;