spl-lang 0.5.0 → 1.0.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/README.md +77 -106
- package/dist/ast.d.ts +10 -0
- package/dist/checkpoint.d.ts +59 -0
- package/dist/checkpoint.js +382 -0
- package/dist/cli.js +176 -35
- package/dist/copilot.js +45 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/interpreter.d.ts +6 -1
- package/dist/interpreter.js +150 -18
- package/dist/lexer.d.ts +2 -0
- package/dist/lexer.js +1 -1
- package/dist/llm.d.ts +4 -1
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +109 -0
- package/dist/parser.d.ts +1 -0
- package/dist/parser.js +49 -15
- package/dist/preamble.d.ts +2 -0
- package/dist/preamble.js +38 -0
- package/dist/recording.d.ts +32 -1
- package/dist/recording.js +94 -19
- package/package.json +4 -2
- package/docs/syntax.ebnf +0 -97
package/dist/cli.js
CHANGED
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { CopilotBackend } from './copilot.js';
|
|
6
7
|
import { AuthError, login, logout } from './auth.js';
|
|
7
8
|
import { Failure, SplSyntaxError } from './errors.js';
|
|
8
9
|
import { LlmError } from './llm.js';
|
|
9
10
|
import { Logger } from './logger.js';
|
|
10
|
-
import {
|
|
11
|
+
import { usesHuman } from './parser.js';
|
|
12
|
+
import { parseMarkdown } from './markdown.js';
|
|
11
13
|
import { run } from './interpreter.js';
|
|
12
14
|
import { JsonFileStore, MemoryStore, loadStoreModule } from './store.js';
|
|
13
15
|
import { toJson } from './values.js';
|
|
14
16
|
import { loadMcpConfig } from './mcp.js';
|
|
15
|
-
import { RecordingBackend, ReplayBackend } from './recording.js';
|
|
17
|
+
import { RecordingBackend, ReplayBackend, TraceBackend } from './recording.js';
|
|
18
|
+
import { Checkpoint, CheckpointError, checkpointFingerprint } from './checkpoint.js';
|
|
16
19
|
const HELP = `spl - Semantic Prompt Language interpreter
|
|
17
20
|
|
|
18
|
-
Usage: spl <
|
|
21
|
+
Usage: spl <workflow.md> [options]
|
|
19
22
|
spl login authenticate with GitHub Copilot (device flow in the browser)
|
|
20
23
|
spl logout remove the credentials saved by 'spl login'
|
|
21
24
|
|
|
@@ -23,21 +26,32 @@ Options:
|
|
|
23
26
|
--log-level <level> error | info | debug (default: info). Logs go to stderr.
|
|
24
27
|
All levels include LLM transcripts and [deterministic] statement traces.
|
|
25
28
|
--silent suppress all logs; only the result is printed
|
|
26
|
-
--model <id> model override (takes precedence over
|
|
29
|
+
--model <id> model override (takes precedence over frontmatter)
|
|
27
30
|
--arg <value> script argument exposed in $args (repeatable; also --arg=value)
|
|
28
31
|
--mcp <file.json> MCP servers in VS Code mcp.json format
|
|
29
32
|
--store <file.json> persist top-level variables in a JSON file
|
|
30
33
|
--store-module <path> custom variable store: module exporting createStore() -> { load(), save(vars) }
|
|
31
34
|
--timeout <seconds> per LLM call timeout (default: 300)
|
|
35
|
+
--trace <file.jsonl> trace all LLM requests, responses, and failures (new file)
|
|
32
36
|
--record <file.jsonl> record generation responses (new file; no agents)
|
|
33
|
-
--replay <file.jsonl>
|
|
37
|
+
--replay <file.jsonl> replay LLM results without Copilot/MCP; explicit script tools still run
|
|
38
|
+
Legacy --record files allow only local reads and no stores.
|
|
39
|
+
--checkpoint <file> start a new resumable run; with --resume, continue that journal
|
|
40
|
+
New runs never overwrite an existing file.
|
|
41
|
+
--resume [file] inspect pending input, or resume with an answer
|
|
42
|
+
Omit file when using --checkpoint <file>.
|
|
43
|
+
--answer-for <id> ID of the pending human request being answered
|
|
44
|
+
--answer <text> exact free-text answer (requires --resume and --answer-for)
|
|
45
|
+
--answer-file <file> read the answer from a UTF-8 file instead of --answer
|
|
34
46
|
--cwd <dir> base directory for relative paths and restrictions (default: current directory)
|
|
35
47
|
--check validate script syntax and any --mcp config without executing
|
|
36
48
|
-v, --version print version
|
|
37
49
|
-h, --help print this help
|
|
38
50
|
|
|
39
51
|
The script result (the value of 'success' or 'return', or the failure payload) is written to stdout.
|
|
40
|
-
|
|
52
|
+
Resumable runs print JSON envelopes and never read stdin.
|
|
53
|
+
Exit codes: 0 success, 1 uncaught script failure, 2 syntax or usage error,
|
|
54
|
+
3 waiting for human input, 70 internal/infrastructure error.`;
|
|
41
55
|
function version() {
|
|
42
56
|
const pkg = new URL('../package.json', import.meta.url);
|
|
43
57
|
return JSON.parse(fs.readFileSync(pkg, 'utf8')).version;
|
|
@@ -46,6 +60,71 @@ function usageError(msg) {
|
|
|
46
60
|
process.stderr.write(`spl: ${msg}\nTry 'spl --help'.\n`);
|
|
47
61
|
process.exit(2);
|
|
48
62
|
}
|
|
63
|
+
async function checkpointCommand(source, program, values, cwd, logger, mcpServers) {
|
|
64
|
+
let checkpoint;
|
|
65
|
+
try {
|
|
66
|
+
const directory = fileURLToPath(new URL('.', import.meta.url));
|
|
67
|
+
const runtime = checkpointFingerprint([
|
|
68
|
+
version(), process.version,
|
|
69
|
+
...fs.readdirSync(directory).filter((name) => name.endsWith('.js')).sort()
|
|
70
|
+
.map((name) => [name, fs.readFileSync(path.join(directory, name), 'utf8')]),
|
|
71
|
+
]);
|
|
72
|
+
const context = {
|
|
73
|
+
source, runtime, cwd, args: values.arg,
|
|
74
|
+
configuration: { model: values.model ?? null, mcp: mcpServers ?? null },
|
|
75
|
+
};
|
|
76
|
+
checkpoint = values.resume !== undefined
|
|
77
|
+
? Checkpoint.resume(values.resume, context)
|
|
78
|
+
: Checkpoint.create(values.checkpoint, context);
|
|
79
|
+
if (values.resume !== undefined) {
|
|
80
|
+
if (values.answer === undefined && values['answer-file'] === undefined) {
|
|
81
|
+
const envelope = { status: 'waiting_for_input', ...checkpoint.request };
|
|
82
|
+
checkpoint.close();
|
|
83
|
+
process.stdout.write(JSON.stringify(envelope) + '\n');
|
|
84
|
+
return 3;
|
|
85
|
+
}
|
|
86
|
+
let answer = values.answer;
|
|
87
|
+
if (values['answer-file'] !== undefined) {
|
|
88
|
+
try {
|
|
89
|
+
answer = fs.readFileSync(values['answer-file'], 'utf8');
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new CheckpointError(`cannot read answer file: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
checkpoint.answer(values['answer-for'], answer);
|
|
96
|
+
}
|
|
97
|
+
const backend = new CopilotBackend({ logger, cwd, timeoutMs: Number(values.timeout ?? 300) * 1000, mcpServers });
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = await run(program, { backend, logger, cwd, model: values.model, checkpoint });
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await backend.close();
|
|
104
|
+
}
|
|
105
|
+
if (result.status === 'waiting_for_input') {
|
|
106
|
+
checkpoint.close();
|
|
107
|
+
process.stdout.write(JSON.stringify({ status: result.status, ...result.request }) + '\n');
|
|
108
|
+
return 3;
|
|
109
|
+
}
|
|
110
|
+
checkpoint.finish(result.status);
|
|
111
|
+
const envelope = result.status === 'success'
|
|
112
|
+
? { status: result.status, run_id: checkpoint.runId, has_value: result.hasValue, value: toJson(result.value) }
|
|
113
|
+
: { status: result.status, run_id: checkpoint.runId, failure: result.failure };
|
|
114
|
+
checkpoint.close();
|
|
115
|
+
process.stdout.write(JSON.stringify(envelope) + '\n');
|
|
116
|
+
return result.status === 'success' ? 0 : 1;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (!(error instanceof CheckpointError) || error.kind !== 'usage')
|
|
120
|
+
throw error;
|
|
121
|
+
process.stderr.write(`spl: ${error.message}\n`);
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
checkpoint?.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
49
128
|
async function loginCommand() {
|
|
50
129
|
try {
|
|
51
130
|
const status = await login((line) => process.stdout.write(line + '\n'));
|
|
@@ -73,25 +152,48 @@ function logoutCommand() {
|
|
|
73
152
|
}
|
|
74
153
|
async function main() {
|
|
75
154
|
let parsed;
|
|
155
|
+
let resumeWithCheckpoint = false;
|
|
76
156
|
try {
|
|
157
|
+
const options = {
|
|
158
|
+
'log-level': { type: 'string' },
|
|
159
|
+
silent: { type: 'boolean' },
|
|
160
|
+
model: { type: 'string' },
|
|
161
|
+
arg: { type: 'string', multiple: true },
|
|
162
|
+
mcp: { type: 'string' },
|
|
163
|
+
store: { type: 'string' },
|
|
164
|
+
'store-module': { type: 'string' },
|
|
165
|
+
timeout: { type: 'string' },
|
|
166
|
+
trace: { type: 'string' },
|
|
167
|
+
record: { type: 'string' },
|
|
168
|
+
replay: { type: 'string' },
|
|
169
|
+
checkpoint: { type: 'string' },
|
|
170
|
+
resume: { type: 'string' },
|
|
171
|
+
'answer-for': { type: 'string' },
|
|
172
|
+
answer: { type: 'string' },
|
|
173
|
+
'answer-file': { type: 'string' },
|
|
174
|
+
cwd: { type: 'string' },
|
|
175
|
+
check: { type: 'boolean' },
|
|
176
|
+
version: { type: 'boolean', short: 'v' },
|
|
177
|
+
help: { type: 'boolean', short: 'h' },
|
|
178
|
+
};
|
|
179
|
+
const args = process.argv.slice(2);
|
|
180
|
+
// parseArgs has no optional string options; locate bare --resume using tokens.
|
|
181
|
+
const { tokens } = parseArgs({
|
|
182
|
+
args, options: { ...options, resume: { type: 'boolean' } },
|
|
183
|
+
allowPositionals: true, strict: false, tokens: true,
|
|
184
|
+
});
|
|
185
|
+
const checkpointHasScript = tokens.some((token) => token.kind === 'option' && token.name === 'checkpoint')
|
|
186
|
+
&& tokens.filter((token) => token.kind === 'positional').length === 1;
|
|
187
|
+
const bareResume = new Set(tokens
|
|
188
|
+
.filter((token) => token.kind === 'option' && token.name === 'resume'
|
|
189
|
+
&& token.value === undefined
|
|
190
|
+
&& (checkpointHasScript || args[token.index + 1] === undefined || args[token.index + 1].startsWith('-')))
|
|
191
|
+
.map((token) => token.index));
|
|
192
|
+
resumeWithCheckpoint = bareResume.size > 0;
|
|
77
193
|
parsed = parseArgs({
|
|
194
|
+
args: args.filter((_, index) => !bareResume.has(index)),
|
|
78
195
|
allowPositionals: true,
|
|
79
|
-
options
|
|
80
|
-
'log-level': { type: 'string' },
|
|
81
|
-
silent: { type: 'boolean' },
|
|
82
|
-
model: { type: 'string' },
|
|
83
|
-
arg: { type: 'string', multiple: true },
|
|
84
|
-
mcp: { type: 'string' },
|
|
85
|
-
store: { type: 'string' },
|
|
86
|
-
'store-module': { type: 'string' },
|
|
87
|
-
timeout: { type: 'string' },
|
|
88
|
-
record: { type: 'string' },
|
|
89
|
-
replay: { type: 'string' },
|
|
90
|
-
cwd: { type: 'string' },
|
|
91
|
-
check: { type: 'boolean' },
|
|
92
|
-
version: { type: 'boolean', short: 'v' },
|
|
93
|
-
help: { type: 'boolean', short: 'h' },
|
|
94
|
-
},
|
|
196
|
+
options,
|
|
95
197
|
});
|
|
96
198
|
}
|
|
97
199
|
catch (e) {
|
|
@@ -122,12 +224,40 @@ async function main() {
|
|
|
122
224
|
usageError('--timeout must be a positive number');
|
|
123
225
|
if (values.store && values['store-module'])
|
|
124
226
|
usageError('--store and --store-module are mutually exclusive');
|
|
125
|
-
if (values.record
|
|
126
|
-
usageError('--record and --replay are mutually exclusive');
|
|
127
|
-
|
|
128
|
-
|
|
227
|
+
if ([values.trace, values.record, values.replay].filter((value) => value !== undefined).length > 1) {
|
|
228
|
+
usageError('--trace, --record, and --replay are mutually exclusive');
|
|
229
|
+
}
|
|
230
|
+
if ([values.trace, values.record, values.replay].some((value) => value !== undefined && value.trim() === '')) {
|
|
231
|
+
usageError('--trace, --record, and --replay require a non-empty file path');
|
|
232
|
+
}
|
|
233
|
+
if (resumeWithCheckpoint) {
|
|
234
|
+
if (values.resume !== undefined)
|
|
235
|
+
usageError('use either --resume <file> or --resume --checkpoint <file>');
|
|
236
|
+
if (values.checkpoint === undefined)
|
|
237
|
+
usageError('--resume requires a file: use --resume <file> or --resume --checkpoint <file>');
|
|
238
|
+
values.resume = values.checkpoint;
|
|
239
|
+
delete values.checkpoint;
|
|
240
|
+
}
|
|
241
|
+
const resumable = values.checkpoint !== undefined || values.resume !== undefined;
|
|
242
|
+
if (values.checkpoint !== undefined && values.resume !== undefined)
|
|
243
|
+
usageError('--checkpoint and --resume are mutually exclusive');
|
|
244
|
+
if ([values.checkpoint, values.resume, values['answer-for'], values['answer-file']].some((value) => value !== undefined && !value.trim())) {
|
|
245
|
+
usageError('checkpoint, resume, answer-for, and answer-file require non-empty values');
|
|
246
|
+
}
|
|
247
|
+
if (resumable && [values.store, values['store-module'], values.trace, values.record, values.replay].some((value) => value !== undefined)) {
|
|
248
|
+
usageError('resumable runs cannot use --store, --store-module, --trace, --record, or --replay');
|
|
249
|
+
}
|
|
250
|
+
const answering = values.answer !== undefined || values['answer-file'] !== undefined;
|
|
251
|
+
if (values.answer !== undefined && values['answer-file'] !== undefined)
|
|
252
|
+
usageError('--answer and --answer-file are mutually exclusive');
|
|
253
|
+
if ((answering || values['answer-for'] !== undefined) && values.resume === undefined)
|
|
254
|
+
usageError('answer flags require --resume');
|
|
255
|
+
if (answering !== (values['answer-for'] !== undefined))
|
|
256
|
+
usageError('an answer requires --answer-for, and --answer-for requires an answer');
|
|
129
257
|
const cwd = path.resolve(values.cwd ?? process.cwd());
|
|
130
258
|
const file = positionals[0];
|
|
259
|
+
if (!/\.md$/i.test(file))
|
|
260
|
+
usageError('only .md workflows are executable; wrap SPL in a fenced spl block and add extension/version frontmatter');
|
|
131
261
|
let source;
|
|
132
262
|
try {
|
|
133
263
|
source = fs.readFileSync(file, 'utf8');
|
|
@@ -137,7 +267,7 @@ async function main() {
|
|
|
137
267
|
}
|
|
138
268
|
let program;
|
|
139
269
|
try {
|
|
140
|
-
program =
|
|
270
|
+
program = parseMarkdown(source);
|
|
141
271
|
}
|
|
142
272
|
catch (e) {
|
|
143
273
|
if (e instanceof SplSyntaxError) {
|
|
@@ -155,13 +285,10 @@ async function main() {
|
|
|
155
285
|
}
|
|
156
286
|
if (values.check)
|
|
157
287
|
return 0;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
catch (e) {
|
|
163
|
-
return usageError(e.message);
|
|
164
|
-
}
|
|
288
|
+
if (resumable)
|
|
289
|
+
return checkpointCommand(source, program, values, cwd, logger, mcpServers);
|
|
290
|
+
if (usesHuman(program))
|
|
291
|
+
usageError('human input requires --checkpoint or --resume');
|
|
165
292
|
let backend;
|
|
166
293
|
try {
|
|
167
294
|
if (values.replay)
|
|
@@ -170,6 +297,8 @@ async function main() {
|
|
|
170
297
|
backend = new CopilotBackend({ logger, cwd, timeoutMs: timeoutSec * 1000, mcpServers });
|
|
171
298
|
if (values.record)
|
|
172
299
|
backend = new RecordingBackend(backend, values.record);
|
|
300
|
+
if (values.trace)
|
|
301
|
+
backend = new TraceBackend(backend, values.trace);
|
|
173
302
|
}
|
|
174
303
|
}
|
|
175
304
|
catch (e) {
|
|
@@ -177,9 +306,19 @@ async function main() {
|
|
|
177
306
|
throw e;
|
|
178
307
|
return usageError(`cannot open recording: ${e.message}`);
|
|
179
308
|
}
|
|
309
|
+
if (backend.mode === 'replay' && (values.store || values['store-module'])) {
|
|
310
|
+
return usageError('--replay of legacy --record files cannot use a persistent variable store');
|
|
311
|
+
}
|
|
312
|
+
let store;
|
|
313
|
+
try {
|
|
314
|
+
store = values.store ? new JsonFileStore(values.store) : values['store-module'] ? await loadStoreModule(values['store-module']) : new MemoryStore();
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
return usageError(e.message);
|
|
318
|
+
}
|
|
180
319
|
let result;
|
|
181
320
|
try {
|
|
182
|
-
result = await run(program, { backend, logger, store:
|
|
321
|
+
result = await run(program, { backend, logger, store: backend.mode === 'replay' ? undefined : store, cwd, model: values.model, args: values.arg });
|
|
183
322
|
}
|
|
184
323
|
finally {
|
|
185
324
|
try {
|
|
@@ -197,6 +336,8 @@ async function main() {
|
|
|
197
336
|
process.stdout.write(JSON.stringify(result.failure, null, 4) + '\n');
|
|
198
337
|
return 1;
|
|
199
338
|
}
|
|
339
|
+
if (result.status === 'waiting_for_input')
|
|
340
|
+
throw new CheckpointError('unexpected human suspension without a checkpoint', 'infrastructure');
|
|
200
341
|
if (result.hasValue) {
|
|
201
342
|
const v = result.value;
|
|
202
343
|
process.stdout.write((typeof v === 'string' ? v : JSON.stringify(toJson(v), null, 2)) + '\n');
|
package/dist/copilot.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CopilotClient, defineTool } from '@github/copilot-sdk';
|
|
2
2
|
import { loadToken } from './auth.js';
|
|
3
|
-
import { Failure } from './errors.js';
|
|
3
|
+
import { Failure, SplSyntaxError } from './errors.js';
|
|
4
|
+
import { parse } from './parser.js';
|
|
4
5
|
import { LlmError } from './llm.js';
|
|
5
6
|
import { TOOL_SPECS } from './tools.js';
|
|
6
7
|
import { configuredMcpTool, filterMcpServers } from './mcp.js';
|
|
@@ -81,7 +82,8 @@ export class CopilotBackend {
|
|
|
81
82
|
violation ??= failure;
|
|
82
83
|
void session?.abort().catch(() => undefined);
|
|
83
84
|
};
|
|
84
|
-
const agent = req.kind === 'agent';
|
|
85
|
+
const agent = req.kind === 'agent' && !req.naturalLanguage;
|
|
86
|
+
let submitted;
|
|
85
87
|
const tools = (agent ? TOOL_NAMES : []).map((name) => {
|
|
86
88
|
const spec = TOOL_SPECS[name];
|
|
87
89
|
return defineTool(name, {
|
|
@@ -105,15 +107,50 @@ export class CopilotBackend {
|
|
|
105
107
|
},
|
|
106
108
|
});
|
|
107
109
|
});
|
|
110
|
+
if (req.naturalLanguage) {
|
|
111
|
+
tools.push(defineTool('submit_spl', {
|
|
112
|
+
description: 'Submit one complete SPL program to execute after this request, in shared document scope. No actions occur during submission.',
|
|
113
|
+
parameters: {
|
|
114
|
+
type: 'object', properties: { source: { type: 'string' } },
|
|
115
|
+
required: ['source'], additionalProperties: false,
|
|
116
|
+
},
|
|
117
|
+
handler: async (args) => {
|
|
118
|
+
if (!args || typeof args !== 'object' || !('source' in args) ||
|
|
119
|
+
typeof args.source !== 'string' || !args.source.trim()) {
|
|
120
|
+
return { resultType: 'failure', textResultForLlm: 'source must be non-empty SPL code' };
|
|
121
|
+
}
|
|
122
|
+
if (submitted !== undefined) {
|
|
123
|
+
return { resultType: 'failure', textResultForLlm: 'a program has already been submitted' };
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const program = parse(args.source);
|
|
127
|
+
if (program.directives.model !== undefined) {
|
|
128
|
+
return { resultType: 'failure', textResultForLlm: 'use model modifiers, not a model header' };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (!(error instanceof SplSyntaxError))
|
|
133
|
+
throw error;
|
|
134
|
+
return { resultType: 'failure', textResultForLlm: error.message };
|
|
135
|
+
}
|
|
136
|
+
submitted = args.source;
|
|
137
|
+
return 'Program accepted for execution after this request. Do not submit again.';
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
108
141
|
const mcpServers = agent ? filterMcpServers(this.opts.mcpServers ?? {}, req.restrictions) : {};
|
|
109
142
|
const baseConfig = {
|
|
110
143
|
model: req.model,
|
|
111
144
|
tools,
|
|
112
|
-
availableTools: [...(
|
|
145
|
+
availableTools: [...tools.map((tool) => tool.name), ...Object.keys(mcpServers).map((name) => `mcp:${name}-*`)],
|
|
113
146
|
mcpServers,
|
|
114
147
|
systemMessage: { mode: 'append', content: SYSTEM_MESSAGE },
|
|
115
148
|
workingDirectory: this.opts.cwd,
|
|
116
|
-
onPermissionRequest: (r) =>
|
|
149
|
+
onPermissionRequest: (r) => {
|
|
150
|
+
if (req.naturalLanguage && r.kind === 'custom-tool' && r.toolName === 'submit_spl')
|
|
151
|
+
return { kind: 'approve-once' };
|
|
152
|
+
return agent ? decidePermission(r, req.restrictions, block, mcpServers) : this.denyGenerationPermission(block);
|
|
153
|
+
},
|
|
117
154
|
};
|
|
118
155
|
try {
|
|
119
156
|
try {
|
|
@@ -143,12 +180,15 @@ export class CopilotBackend {
|
|
|
143
180
|
}
|
|
144
181
|
if (violation)
|
|
145
182
|
throw violation;
|
|
146
|
-
|
|
183
|
+
if (req.naturalLanguage && submitted === undefined)
|
|
184
|
+
throw new LlmError('natural-language request did not call submit_spl');
|
|
185
|
+
const text = answer?.data?.content ?? (submitted !== undefined ? 'SPL program submitted.' : undefined);
|
|
147
186
|
if (typeof text !== 'string' || text.trim() === '')
|
|
148
187
|
throw new LlmError('the model returned an empty response');
|
|
149
188
|
const usedModel = answer?.data?.model;
|
|
150
189
|
return {
|
|
151
190
|
text,
|
|
191
|
+
...(submitted !== undefined ? { spl: submitted } : {}),
|
|
152
192
|
reasoning: reasoning.length ? reasoning.join('\n') : undefined,
|
|
153
193
|
effective: {
|
|
154
194
|
backend: 'copilot-sdk',
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { parse } from './parser.js';
|
|
1
|
+
export { parse, usesHuman } from './parser.js';
|
|
2
|
+
export { parseMarkdown, extractMarkdown, isWorkflowMarkdown } from './markdown.js';
|
|
2
3
|
export { run, type RunOptions, type RunResult } from './interpreter.js';
|
|
3
4
|
export { Failure, SplSyntaxError, type FailurePayload, type StatusCode } from './errors.js';
|
|
4
5
|
export { Logger, type LogLevel } from './logger.js';
|
|
5
6
|
export { CopilotBackend } from './copilot.js';
|
|
6
|
-
export { RecordingBackend, ReplayBackend } from './recording.js';
|
|
7
|
+
export { RecordingBackend, ReplayBackend, TraceBackend } from './recording.js';
|
|
8
|
+
export { Checkpoint, CheckpointError, checkpointFingerprint, type CheckpointContext, type HumanRequest } from './checkpoint.js';
|
|
7
9
|
export { loadMcpConfig, type McpServers } from './mcp.js';
|
|
8
10
|
export { login, logout, authStatus, AuthError } from './auth.js';
|
|
9
11
|
export { JsonFileStore, MemoryStore, loadStoreModule, type VariableStore } from './store.js';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { parse } from './parser.js';
|
|
1
|
+
export { parse, usesHuman } from './parser.js';
|
|
2
|
+
export { parseMarkdown, extractMarkdown, isWorkflowMarkdown } from './markdown.js';
|
|
2
3
|
export { run } from './interpreter.js';
|
|
3
4
|
export { Failure, SplSyntaxError } from './errors.js';
|
|
4
5
|
export { Logger } from './logger.js';
|
|
5
6
|
export { CopilotBackend } from './copilot.js';
|
|
6
|
-
export { RecordingBackend, ReplayBackend } from './recording.js';
|
|
7
|
+
export { RecordingBackend, ReplayBackend, TraceBackend } from './recording.js';
|
|
8
|
+
export { Checkpoint, CheckpointError, checkpointFingerprint } from './checkpoint.js';
|
|
7
9
|
export { loadMcpConfig } from './mcp.js';
|
|
8
10
|
export { login, logout, authStatus, AuthError } from './auth.js';
|
|
9
11
|
export { JsonFileStore, MemoryStore, loadStoreModule } from './store.js';
|
package/dist/interpreter.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type FailurePayload } from './errors.js';
|
|
|
3
3
|
import { type LlmBackend } from './llm.js';
|
|
4
4
|
import type { Logger } from './logger.js';
|
|
5
5
|
import { type VariableStore } from './store.js';
|
|
6
|
+
import { type Checkpoint, type HumanRequest } from './checkpoint.js';
|
|
6
7
|
import { type Value } from './values.js';
|
|
7
8
|
export interface RunOptions {
|
|
8
9
|
backend: LlmBackend;
|
|
@@ -10,11 +11,12 @@ export interface RunOptions {
|
|
|
10
11
|
store?: VariableStore;
|
|
11
12
|
/** Working directory for relative paths, `restrict` folders and tools. */
|
|
12
13
|
cwd?: string;
|
|
13
|
-
/** Overrides the `@
|
|
14
|
+
/** Overrides the `@(model=...)` directive. */
|
|
14
15
|
model?: string;
|
|
15
16
|
/** Script arguments exposed as read-only `$args`; defaults to an empty list. */
|
|
16
17
|
args?: readonly string[];
|
|
17
18
|
maxCallDepth?: number;
|
|
19
|
+
checkpoint?: Checkpoint;
|
|
18
20
|
}
|
|
19
21
|
export type RunResult = {
|
|
20
22
|
status: 'success';
|
|
@@ -23,5 +25,8 @@ export type RunResult = {
|
|
|
23
25
|
} | {
|
|
24
26
|
status: 'failure';
|
|
25
27
|
failure: FailurePayload;
|
|
28
|
+
} | {
|
|
29
|
+
status: 'waiting_for_input';
|
|
30
|
+
request: HumanRequest;
|
|
26
31
|
};
|
|
27
32
|
export declare function run(program: Program, opts: RunOptions): Promise<RunResult>;
|