@contentful/experience-design-system-generation 2.36.2-dev-build-d983d04c.0 → 2.36.2-dev-build-dc6fb2aa.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package.json +1 -1
- package/dist/src/agent-names.d.ts +1 -0
- package/dist/src/agent-names.js +7 -0
- package/dist/src/controller/create-generation-endpoint.d.ts +3 -0
- package/dist/src/controller/create-generation-endpoint.js +15 -0
- package/dist/src/controller/helpers/validate-generation-request.d.ts +2 -0
- package/dist/src/controller/helpers/validate-generation-request.js +18 -0
- package/dist/src/index.d.ts +19 -7
- package/dist/src/index.js +18 -6
- package/dist/src/orchestrator/execute-generation-orchestrator.d.ts +6 -0
- package/dist/src/orchestrator/execute-generation-orchestrator.js +12 -0
- package/dist/src/services/agent/helpers/build-agent-args.d.ts +6 -0
- package/dist/src/services/agent/helpers/build-agent-args.js +58 -0
- package/dist/src/services/agent/helpers/check-agent-auth.d.ts +3 -0
- package/dist/src/services/agent/helpers/check-agent-auth.js +19 -0
- package/dist/src/services/agent/helpers/check-claude-auth.d.ts +2 -0
- package/dist/src/services/agent/helpers/check-claude-auth.js +37 -0
- package/dist/src/services/agent/helpers/describe-agent-failure.d.ts +2 -0
- package/dist/src/services/agent/helpers/describe-agent-failure.js +5 -0
- package/dist/src/services/agent/helpers/extract-sentinel-output.d.ts +1 -0
- package/dist/src/services/agent/helpers/extract-sentinel-output.js +16 -0
- package/dist/src/services/agent/helpers/resolve-agent-binary.d.ts +2 -0
- package/dist/src/services/agent/helpers/resolve-agent-binary.js +8 -0
- package/dist/src/services/agent/local-cli-agent-invoker.d.ts +3 -0
- package/dist/src/{agent-invoker.js → services/agent/local-cli-agent-invoker.js} +2 -2
- package/dist/src/services/agent/run-agent.d.ts +15 -0
- package/dist/src/services/agent/run-agent.js +70 -0
- package/dist/src/services/parsing/helpers/read-tool-call-objects.d.ts +4 -0
- package/dist/src/services/parsing/helpers/read-tool-call-objects.js +54 -0
- package/dist/src/services/parsing/parse-map-token-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-map-token-tool-calls.js +39 -0
- package/dist/src/services/parsing/parse-prop-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-prop-tool-calls.js +91 -0
- package/dist/src/services/parsing/parse-select-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-select-tool-calls.js +27 -0
- package/dist/src/services/parsing/parse-token-tool-calls.d.ts +2 -0
- package/dist/src/services/parsing/parse-token-tool-calls.js +39 -0
- package/dist/src/services/prompts/build-prompt.d.ts +4 -0
- package/dist/src/services/prompts/build-prompt.js +8 -0
- package/dist/src/services/prompts/helpers/build-preamble.d.ts +2 -0
- package/dist/src/services/prompts/helpers/build-preamble.js +48 -0
- package/dist/src/services/prompts/helpers/build-skill-preambles.d.ts +4 -0
- package/dist/src/services/prompts/helpers/build-skill-preambles.js +126 -0
- package/dist/src/services/prompts/helpers/format-source-refs.d.ts +2 -0
- package/dist/src/services/prompts/helpers/format-source-refs.js +31 -0
- package/dist/src/services/prompts/helpers/format-token-candidates.d.ts +8 -0
- package/dist/src/services/prompts/helpers/format-token-candidates.js +80 -0
- package/dist/src/services/prompts/helpers/resolve-skill-file.d.ts +4 -0
- package/dist/src/services/prompts/helpers/resolve-skill-file.js +49 -0
- package/dist/src/types/agent.d.ts +10 -0
- package/dist/src/types/agent.js +1 -0
- package/dist/src/types/contract.d.ts +24 -0
- package/dist/src/types/contract.js +1 -0
- package/dist/src/types/invoker.d.ts +17 -0
- package/dist/src/types/invoker.js +1 -0
- package/dist/src/types/prompt.d.ts +53 -0
- package/dist/src/types/prompt.js +1 -0
- package/dist/src/types/tool-calls.d.ts +91 -0
- package/dist/src/types/tool-calls.js +1 -0
- package/package.json +2 -2
- package/dist/src/agent-invoker.d.ts +0 -25
- package/dist/src/agent-runner.d.ts +0 -144
- package/dist/src/agent-runner.js +0 -564
- package/dist/src/prompt-builder.d.ts +0 -81
- package/dist/src/prompt-builder.js +0 -344
package/dist/src/agent-runner.js
DELETED
|
@@ -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;
|