spl-lang 0.6.0 → 1.0.1
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 +153 -22
- package/dist/copilot.js +65 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/interpreter.d.ts +6 -1
- package/dist/interpreter.js +150 -17
- package/dist/lexer.d.ts +2 -0
- package/dist/lexer.js +1 -1
- package/dist/llm.d.ts +5 -0
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +109 -0
- package/dist/parser.d.ts +6 -0
- package/dist/parser.js +70 -15
- package/dist/preamble.d.ts +2 -0
- package/dist/preamble.js +42 -0
- package/dist/recording.d.ts +23 -0
- package/dist/recording.js +10 -7
- 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
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,7 +26,7 @@ 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
|
|
@@ -33,13 +36,22 @@ Options:
|
|
|
33
36
|
--record <file.jsonl> record generation responses (new file; no agents)
|
|
34
37
|
--replay <file.jsonl> replay LLM results without Copilot/MCP; explicit script tools still run
|
|
35
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
|
|
36
46
|
--cwd <dir> base directory for relative paths and restrictions (default: current directory)
|
|
37
47
|
--check validate script syntax and any --mcp config without executing
|
|
38
48
|
-v, --version print version
|
|
39
49
|
-h, --help print this help
|
|
40
50
|
|
|
41
51
|
The script result (the value of 'success' or 'return', or the failure payload) is written to stdout.
|
|
42
|
-
|
|
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.`;
|
|
43
55
|
function version() {
|
|
44
56
|
const pkg = new URL('../package.json', import.meta.url);
|
|
45
57
|
return JSON.parse(fs.readFileSync(pkg, 'utf8')).version;
|
|
@@ -48,6 +60,71 @@ function usageError(msg) {
|
|
|
48
60
|
process.stderr.write(`spl: ${msg}\nTry 'spl --help'.\n`);
|
|
49
61
|
process.exit(2);
|
|
50
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
|
+
}
|
|
51
128
|
async function loginCommand() {
|
|
52
129
|
try {
|
|
53
130
|
const status = await login((line) => process.stdout.write(line + '\n'));
|
|
@@ -75,26 +152,48 @@ function logoutCommand() {
|
|
|
75
152
|
}
|
|
76
153
|
async function main() {
|
|
77
154
|
let parsed;
|
|
155
|
+
let resumeWithCheckpoint = false;
|
|
78
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;
|
|
79
193
|
parsed = parseArgs({
|
|
194
|
+
args: args.filter((_, index) => !bareResume.has(index)),
|
|
80
195
|
allowPositionals: true,
|
|
81
|
-
options
|
|
82
|
-
'log-level': { type: 'string' },
|
|
83
|
-
silent: { type: 'boolean' },
|
|
84
|
-
model: { type: 'string' },
|
|
85
|
-
arg: { type: 'string', multiple: true },
|
|
86
|
-
mcp: { type: 'string' },
|
|
87
|
-
store: { type: 'string' },
|
|
88
|
-
'store-module': { type: 'string' },
|
|
89
|
-
timeout: { type: 'string' },
|
|
90
|
-
trace: { type: 'string' },
|
|
91
|
-
record: { type: 'string' },
|
|
92
|
-
replay: { type: 'string' },
|
|
93
|
-
cwd: { type: 'string' },
|
|
94
|
-
check: { type: 'boolean' },
|
|
95
|
-
version: { type: 'boolean', short: 'v' },
|
|
96
|
-
help: { type: 'boolean', short: 'h' },
|
|
97
|
-
},
|
|
196
|
+
options,
|
|
98
197
|
});
|
|
99
198
|
}
|
|
100
199
|
catch (e) {
|
|
@@ -131,8 +230,34 @@ async function main() {
|
|
|
131
230
|
if ([values.trace, values.record, values.replay].some((value) => value !== undefined && value.trim() === '')) {
|
|
132
231
|
usageError('--trace, --record, and --replay require a non-empty file path');
|
|
133
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');
|
|
134
257
|
const cwd = path.resolve(values.cwd ?? process.cwd());
|
|
135
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');
|
|
136
261
|
let source;
|
|
137
262
|
try {
|
|
138
263
|
source = fs.readFileSync(file, 'utf8');
|
|
@@ -142,7 +267,7 @@ async function main() {
|
|
|
142
267
|
}
|
|
143
268
|
let program;
|
|
144
269
|
try {
|
|
145
|
-
program =
|
|
270
|
+
program = parseMarkdown(source);
|
|
146
271
|
}
|
|
147
272
|
catch (e) {
|
|
148
273
|
if (e instanceof SplSyntaxError) {
|
|
@@ -160,6 +285,10 @@ async function main() {
|
|
|
160
285
|
}
|
|
161
286
|
if (values.check)
|
|
162
287
|
return 0;
|
|
288
|
+
if (resumable)
|
|
289
|
+
return checkpointCommand(source, program, values, cwd, logger, mcpServers);
|
|
290
|
+
if (usesHuman(program))
|
|
291
|
+
usageError('human input requires --checkpoint or --resume');
|
|
163
292
|
let backend;
|
|
164
293
|
try {
|
|
165
294
|
if (values.replay)
|
|
@@ -207,6 +336,8 @@ async function main() {
|
|
|
207
336
|
process.stdout.write(JSON.stringify(result.failure, null, 4) + '\n');
|
|
208
337
|
return 1;
|
|
209
338
|
}
|
|
339
|
+
if (result.status === 'waiting_for_input')
|
|
340
|
+
throw new CheckpointError('unexpected human suspension without a checkpoint', 'infrastructure');
|
|
210
341
|
if (result.hasValue) {
|
|
211
342
|
const v = result.value;
|
|
212
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 { blockLocalDeclarations, 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';
|
|
@@ -10,6 +11,8 @@ const SYSTEM_MESSAGE = [
|
|
|
10
11
|
'Use the provided runtime tools and configured MCP tools only when the task needs them.',
|
|
11
12
|
].join(' ');
|
|
12
13
|
const TOOL_NAMES = Object.keys(TOOL_SPECS);
|
|
14
|
+
const SUBMIT_REMINDER = 'No program has been accepted yet. Do not answer in prose: translate the blockquote into SPL ' +
|
|
15
|
+
'and call submit_spl now with one complete program, fixing any error reported for an earlier submission.';
|
|
13
16
|
export class CopilotBackend {
|
|
14
17
|
opts;
|
|
15
18
|
client;
|
|
@@ -81,7 +84,8 @@ export class CopilotBackend {
|
|
|
81
84
|
violation ??= failure;
|
|
82
85
|
void session?.abort().catch(() => undefined);
|
|
83
86
|
};
|
|
84
|
-
const agent = req.kind === 'agent';
|
|
87
|
+
const agent = req.kind === 'agent' && !req.naturalLanguage;
|
|
88
|
+
let submitted;
|
|
85
89
|
const tools = (agent ? TOOL_NAMES : []).map((name) => {
|
|
86
90
|
const spec = TOOL_SPECS[name];
|
|
87
91
|
return defineTool(name, {
|
|
@@ -105,15 +109,61 @@ export class CopilotBackend {
|
|
|
105
109
|
},
|
|
106
110
|
});
|
|
107
111
|
});
|
|
112
|
+
if (req.naturalLanguage) {
|
|
113
|
+
tools.push(defineTool('submit_spl', {
|
|
114
|
+
description: 'Submit one complete SPL program to execute after this request, in shared document scope. No actions occur during submission.',
|
|
115
|
+
parameters: {
|
|
116
|
+
type: 'object', properties: { source: { type: 'string' } },
|
|
117
|
+
required: ['source'], additionalProperties: false,
|
|
118
|
+
},
|
|
119
|
+
handler: async (args) => {
|
|
120
|
+
if (!args || typeof args !== 'object' || !('source' in args) ||
|
|
121
|
+
typeof args.source !== 'string' || !args.source.trim()) {
|
|
122
|
+
return { resultType: 'failure', textResultForLlm: 'source must be non-empty SPL code' };
|
|
123
|
+
}
|
|
124
|
+
if (submitted !== undefined) {
|
|
125
|
+
return { resultType: 'failure', textResultForLlm: 'a program has already been submitted' };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const program = parse(args.source);
|
|
129
|
+
if (program.directives.model !== undefined) {
|
|
130
|
+
return { resultType: 'failure', textResultForLlm: 'use model modifiers, not a model header' };
|
|
131
|
+
}
|
|
132
|
+
const local = blockLocalDeclarations(program);
|
|
133
|
+
const hidden = (req.markers ?? []).filter((name) => local.has(name));
|
|
134
|
+
if (hidden.length) {
|
|
135
|
+
const list = hidden.join(', ');
|
|
136
|
+
return {
|
|
137
|
+
resultType: 'failure',
|
|
138
|
+
textResultForLlm: `${list} would be declared with let inside a block and disappear when the block ends. ` +
|
|
139
|
+
`Declare ${list} at the top level first (for example let ${hidden[0]} = None, or reuse the existing binding) ` +
|
|
140
|
+
`and assign it inside the block without let. Submit the corrected program.`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (!(error instanceof SplSyntaxError))
|
|
146
|
+
throw error;
|
|
147
|
+
return { resultType: 'failure', textResultForLlm: error.message };
|
|
148
|
+
}
|
|
149
|
+
submitted = args.source;
|
|
150
|
+
return 'Program accepted for execution after this request. Do not submit again.';
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
108
154
|
const mcpServers = agent ? filterMcpServers(this.opts.mcpServers ?? {}, req.restrictions) : {};
|
|
109
155
|
const baseConfig = {
|
|
110
156
|
model: req.model,
|
|
111
157
|
tools,
|
|
112
|
-
availableTools: [...(
|
|
158
|
+
availableTools: [...tools.map((tool) => tool.name), ...Object.keys(mcpServers).map((name) => `mcp:${name}-*`)],
|
|
113
159
|
mcpServers,
|
|
114
160
|
systemMessage: { mode: 'append', content: SYSTEM_MESSAGE },
|
|
115
161
|
workingDirectory: this.opts.cwd,
|
|
116
|
-
onPermissionRequest: (r) =>
|
|
162
|
+
onPermissionRequest: (r) => {
|
|
163
|
+
if (req.naturalLanguage && r.kind === 'custom-tool' && r.toolName === 'submit_spl')
|
|
164
|
+
return { kind: 'approve-once' };
|
|
165
|
+
return agent ? decidePermission(r, req.restrictions, block, mcpServers) : this.denyGenerationPermission(block);
|
|
166
|
+
},
|
|
117
167
|
};
|
|
118
168
|
try {
|
|
119
169
|
try {
|
|
@@ -135,6 +185,10 @@ export class CopilotBackend {
|
|
|
135
185
|
let answer;
|
|
136
186
|
try {
|
|
137
187
|
answer = await session.sendAndWait({ prompt: req.prompt }, this.opts.timeoutMs);
|
|
188
|
+
// Models occasionally reply in prose or give up after a rejected submission; ask once more.
|
|
189
|
+
if (req.naturalLanguage && submitted === undefined && !violation) {
|
|
190
|
+
answer = await session.sendAndWait({ prompt: SUBMIT_REMINDER }, this.opts.timeoutMs);
|
|
191
|
+
}
|
|
138
192
|
}
|
|
139
193
|
catch (e) {
|
|
140
194
|
if (violation)
|
|
@@ -143,12 +197,18 @@ export class CopilotBackend {
|
|
|
143
197
|
}
|
|
144
198
|
if (violation)
|
|
145
199
|
throw violation;
|
|
146
|
-
|
|
200
|
+
if (req.naturalLanguage && submitted === undefined)
|
|
201
|
+
throw new LlmError('natural-language request did not call submit_spl');
|
|
202
|
+
// After submitting, models often end their turn with an empty or missing message.
|
|
203
|
+
const content = answer?.data?.content;
|
|
204
|
+
const text = submitted !== undefined && (typeof content !== 'string' || content.trim() === '')
|
|
205
|
+
? 'SPL program submitted.' : content;
|
|
147
206
|
if (typeof text !== 'string' || text.trim() === '')
|
|
148
207
|
throw new LlmError('the model returned an empty response');
|
|
149
208
|
const usedModel = answer?.data?.model;
|
|
150
209
|
return {
|
|
151
210
|
text,
|
|
211
|
+
...(submitted !== undefined ? { spl: submitted } : {}),
|
|
152
212
|
reasoning: reasoning.length ? reasoning.join('\n') : undefined,
|
|
153
213
|
effective: {
|
|
154
214
|
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
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
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>;
|