@neutrome/lilsdk 0.3.5
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 +31 -0
- package/package.json +27 -0
- package/src/index.ts +14 -0
- package/src/loops/index.ts +266 -0
- package/src/managed/index.ts +18 -0
- package/src/managed/target-executor.ts +12 -0
- package/src/managed/two-pass-request.ts +164 -0
- package/src/managed/twoPassExecutor.ts +200 -0
- package/src/observe.ts +95 -0
- package/src/output.ts +31 -0
- package/src/primitives/index.ts +154 -0
- package/src/stream/index.ts +8 -0
- package/src/synthetic/index.ts +134 -0
- package/src/tools-support.ts +108 -0
- package/src/tools.ts +279 -0
- package/src/types.ts +101 -0
- package/test/lilsdk-ts.test.ts +660 -0
- package/test/tools.test.ts +452 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# `@neutrome/lilsdk`
|
|
2
|
+
|
|
3
|
+
High-level TypeScript SDK for transport-neutral model authoring on top of `@neutrome/lil-engine`.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createTwoPassExecutor, type Executor } from "@neutrome/lilsdk";
|
|
7
|
+
import { retry } from "@neutrome/lilsdk/loops";
|
|
8
|
+
import {
|
|
9
|
+
observeExecutionStream,
|
|
10
|
+
writeReasoning,
|
|
11
|
+
} from "@neutrome/lilsdk/stream";
|
|
12
|
+
import { connectTools } from "@neutrome/lilsdk/tools";
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This package owns:
|
|
16
|
+
|
|
17
|
+
- executor and tool contracts
|
|
18
|
+
- `@neutrome/lilsdk/primitives`: structural program helpers such as `walk`, `map`, `reduce`, `has`, `find`, `indexOf`, `all`, and `any`
|
|
19
|
+
- `@neutrome/lilsdk/synthetic`: request/message synthesis helpers such as `createModelProgram`, `makeMessage`, `makeToolCall`, `makeToolResponse`, and `appendToolInteraction`
|
|
20
|
+
- `@neutrome/lilsdk/tools`: `connectTools` tool-call loop
|
|
21
|
+
- `@neutrome/lilsdk/stream`: stream observation and output helpers
|
|
22
|
+
- `@neutrome/lilsdk/loops`: `retry`, `fallback`, and `createGoalExecutor` control-flow helpers
|
|
23
|
+
- `@neutrome/lilsdk/managed/twoPassExecutor`: `createTwoPassExecutor` and model invocation helpers
|
|
24
|
+
- multi-pass authoring primitives used by virtual models
|
|
25
|
+
|
|
26
|
+
The root export is intentionally small: SDK contracts plus the most common
|
|
27
|
+
high-level two-pass executor helper.
|
|
28
|
+
|
|
29
|
+
It intentionally does not own HTTP routing, provider credentials, target
|
|
30
|
+
resolution, or provider JSON conversion. Those live in `@neutrome/open-ai-router`
|
|
31
|
+
and `@neutrome/lil-engine`.
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neutrome/lilsdk",
|
|
3
|
+
"version": "0.3.5",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts",
|
|
7
|
+
"./types": "./src/types.ts",
|
|
8
|
+
"./primitives": "./src/primitives/index.ts",
|
|
9
|
+
"./synthetic": "./src/synthetic/index.ts",
|
|
10
|
+
"./stream": "./src/stream/index.ts",
|
|
11
|
+
"./tools": "./src/tools.ts",
|
|
12
|
+
"./loops": "./src/loops/index.ts",
|
|
13
|
+
"./managed": "./src/managed/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@neutrome/lil-engine": "0.3.4"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^25.9.3",
|
|
20
|
+
"typescript": "^6.0.3",
|
|
21
|
+
"vitest": "^4.1.6"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
ExecutionTarget,
|
|
3
|
+
Executor,
|
|
4
|
+
ExecutorContext,
|
|
5
|
+
InvokeOptions,
|
|
6
|
+
OutputSink,
|
|
7
|
+
ProgramTransform,
|
|
8
|
+
ExecutionEvent,
|
|
9
|
+
ExecutionEventInput,
|
|
10
|
+
Tool,
|
|
11
|
+
TransformCapability,
|
|
12
|
+
TransformContext,
|
|
13
|
+
} from "./types.ts";
|
|
14
|
+
export { createExecutionEvent } from "./types.ts";
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { createProgram, deltaText, type Program } from "@neutrome/lil-engine";
|
|
2
|
+
import type { Executor, ExecutorContext } from "../types.ts";
|
|
3
|
+
import { appendAssistantMessage } from "../synthetic/index.ts";
|
|
4
|
+
|
|
5
|
+
export type RetryOptions = {
|
|
6
|
+
attempts?: number;
|
|
7
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
|
|
8
|
+
onRetry?: (error: unknown, attempt: number) => void | Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type FallbackOptions = {
|
|
12
|
+
shouldFallback?: (
|
|
13
|
+
error: unknown,
|
|
14
|
+
executorIndex: number,
|
|
15
|
+
) => boolean | Promise<boolean>;
|
|
16
|
+
onFallback?: (error: unknown, executorIndex: number) => void | Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type GoalExecutorOptions = {
|
|
20
|
+
draft: Executor;
|
|
21
|
+
review: Executor;
|
|
22
|
+
refine: (
|
|
23
|
+
request: Program,
|
|
24
|
+
answer: Program,
|
|
25
|
+
review: Program,
|
|
26
|
+
attempt: number,
|
|
27
|
+
) => Program | Promise<Program>;
|
|
28
|
+
satisfied: (review: Program, attempt: number) => boolean | Promise<boolean>;
|
|
29
|
+
maxIterations?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function retry(
|
|
33
|
+
executor: Executor,
|
|
34
|
+
options: RetryOptions = {},
|
|
35
|
+
): Executor {
|
|
36
|
+
const attempts = positiveInteger(options.attempts ?? 2, "retry attempts");
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
execute(request, ctx) {
|
|
40
|
+
return runRetry(
|
|
41
|
+
(attempt) => executor.execute(request, childContext(ctx, attempt)),
|
|
42
|
+
attempts,
|
|
43
|
+
options,
|
|
44
|
+
);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async *stream(request, ctx) {
|
|
48
|
+
let lastError: unknown;
|
|
49
|
+
|
|
50
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
51
|
+
let emitted = false;
|
|
52
|
+
try {
|
|
53
|
+
for await (const chunk of executor.stream(
|
|
54
|
+
request,
|
|
55
|
+
childContext(ctx, attempt),
|
|
56
|
+
)) {
|
|
57
|
+
emitted = true;
|
|
58
|
+
yield chunk;
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
lastError = error;
|
|
63
|
+
if (emitted) throw error;
|
|
64
|
+
const canRetry =
|
|
65
|
+
attempt < attempts &&
|
|
66
|
+
(await shouldContinue(error, attempt, options.shouldRetry));
|
|
67
|
+
if (!canRetry) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
await options.onRetry?.(error, attempt);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw lastError;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function fallback(
|
|
80
|
+
executors: readonly Executor[],
|
|
81
|
+
options: FallbackOptions = {},
|
|
82
|
+
): Executor {
|
|
83
|
+
if (executors.length === 0) {
|
|
84
|
+
throw new Error("fallback requires at least one executor");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
execute(request, ctx) {
|
|
89
|
+
return runFallback(
|
|
90
|
+
(executor, index) =>
|
|
91
|
+
executor.execute(request, childContext(ctx, index + 1)),
|
|
92
|
+
executors,
|
|
93
|
+
options,
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async *stream(request, ctx) {
|
|
98
|
+
let lastError: unknown;
|
|
99
|
+
|
|
100
|
+
for (const [index, executor] of executors.entries()) {
|
|
101
|
+
let emitted = false;
|
|
102
|
+
try {
|
|
103
|
+
for await (const chunk of executor.stream(
|
|
104
|
+
request,
|
|
105
|
+
childContext(ctx, index + 1),
|
|
106
|
+
)) {
|
|
107
|
+
emitted = true;
|
|
108
|
+
yield chunk;
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
lastError = error;
|
|
113
|
+
if (emitted) throw error;
|
|
114
|
+
const canFallback =
|
|
115
|
+
index < executors.length - 1 &&
|
|
116
|
+
(await shouldContinue(error, index, options.shouldFallback));
|
|
117
|
+
if (!canFallback) {
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
await options.onFallback?.(error, index);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw lastError;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createGoalExecutor(options: GoalExecutorOptions): Executor {
|
|
130
|
+
const maxIterations = positiveInteger(
|
|
131
|
+
options.maxIterations ?? 3,
|
|
132
|
+
"goal maxIterations",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
async execute(request, ctx) {
|
|
137
|
+
let current = request;
|
|
138
|
+
let answer = request;
|
|
139
|
+
|
|
140
|
+
for (let attempt = 1; attempt <= maxIterations; attempt += 1) {
|
|
141
|
+
const attemptContext = childContext(ctx, attempt);
|
|
142
|
+
answer = await options.draft.execute(current, attemptContext);
|
|
143
|
+
const review = await options.review.execute(answer, attemptContext);
|
|
144
|
+
if (await options.satisfied(review, attempt)) return answer;
|
|
145
|
+
if (attempt < maxIterations) {
|
|
146
|
+
current = await options.refine(current, answer, review, attempt);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return answer;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async *stream(request, ctx) {
|
|
154
|
+
let current = request;
|
|
155
|
+
let answer = request;
|
|
156
|
+
|
|
157
|
+
for (let attempt = 1; attempt <= maxIterations; attempt += 1) {
|
|
158
|
+
const attemptContext = childContext(ctx, attempt);
|
|
159
|
+
answer = yield* streamGoalStage(
|
|
160
|
+
options.draft.stream(current, attemptContext),
|
|
161
|
+
current,
|
|
162
|
+
);
|
|
163
|
+
const review = yield* streamGoalStage(
|
|
164
|
+
options.review.stream(answer, attemptContext),
|
|
165
|
+
answer,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (await options.satisfied(review, attempt)) return;
|
|
169
|
+
if (attempt < maxIterations) {
|
|
170
|
+
current = await options.refine(current, answer, review, attempt);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function* streamGoalStage(
|
|
178
|
+
source: AsyncIterable<Program>,
|
|
179
|
+
fallback: Program,
|
|
180
|
+
): AsyncGenerator<Program, Program> {
|
|
181
|
+
let final = fallback;
|
|
182
|
+
let transcript = "";
|
|
183
|
+
|
|
184
|
+
for await (const chunk of source) {
|
|
185
|
+
final = chunk;
|
|
186
|
+
transcript += deltaText(chunk);
|
|
187
|
+
yield chunk;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return transcript
|
|
191
|
+
? appendAssistantMessage(createProgram(), transcript)
|
|
192
|
+
: final;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function runRetry<T>(
|
|
196
|
+
operation: (attempt: number) => Promise<T>,
|
|
197
|
+
attempts: number,
|
|
198
|
+
options: RetryOptions,
|
|
199
|
+
): Promise<T> {
|
|
200
|
+
let lastError: unknown;
|
|
201
|
+
|
|
202
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
203
|
+
try {
|
|
204
|
+
return await operation(attempt);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
lastError = error;
|
|
207
|
+
const canRetry =
|
|
208
|
+
attempt < attempts &&
|
|
209
|
+
(await shouldContinue(error, attempt, options.shouldRetry));
|
|
210
|
+
if (!canRetry) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
await options.onRetry?.(error, attempt);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
throw lastError;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function runFallback<T>(
|
|
221
|
+
operation: (executor: Executor, index: number) => Promise<T>,
|
|
222
|
+
executors: readonly Executor[],
|
|
223
|
+
options: FallbackOptions,
|
|
224
|
+
): Promise<T> {
|
|
225
|
+
let lastError: unknown;
|
|
226
|
+
|
|
227
|
+
for (const [index, executor] of executors.entries()) {
|
|
228
|
+
try {
|
|
229
|
+
return await operation(executor, index);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
lastError = error;
|
|
232
|
+
const canFallback =
|
|
233
|
+
index < executors.length - 1 &&
|
|
234
|
+
(await shouldContinue(error, index, options.shouldFallback));
|
|
235
|
+
if (!canFallback) {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
await options.onFallback?.(error, index);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
throw lastError;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function shouldContinue(
|
|
246
|
+
error: unknown,
|
|
247
|
+
attempt: number,
|
|
248
|
+
predicate?: (error: unknown, attempt: number) => boolean | Promise<boolean>,
|
|
249
|
+
): Promise<boolean> {
|
|
250
|
+
return predicate ? predicate(error, attempt) : true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function childContext(ctx: ExecutorContext, attempt: number): ExecutorContext {
|
|
254
|
+
return {
|
|
255
|
+
...ctx,
|
|
256
|
+
executionId: `${ctx.executionId}:${attempt}`,
|
|
257
|
+
parentExecutionId: ctx.executionId,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function positiveInteger(value: number, label: string): number {
|
|
262
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
263
|
+
throw new Error(`${label} must be a positive integer`);
|
|
264
|
+
}
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { createTargetExecutor } from "./target-executor.ts";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
appendInternalDraft,
|
|
5
|
+
createTwoPassExecutor,
|
|
6
|
+
INTERNAL_DRAFT_CALL_ID,
|
|
7
|
+
INTERNAL_DRAFT_TOOL_NAME,
|
|
8
|
+
invokeExecutor,
|
|
9
|
+
streamExecutor,
|
|
10
|
+
} from "./twoPassExecutor.ts";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
InternalDraft,
|
|
14
|
+
ModelExecutor,
|
|
15
|
+
TwoPassExecutorOptions,
|
|
16
|
+
TwoPassSettings,
|
|
17
|
+
TwoPassSettingsResolver,
|
|
18
|
+
} from "./twoPassExecutor.ts";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExecutionTarget, Executor } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export function createTargetExecutor(target: ExecutionTarget): Executor {
|
|
4
|
+
return {
|
|
5
|
+
execute(request, context) {
|
|
6
|
+
return context.invoke(request, { target });
|
|
7
|
+
},
|
|
8
|
+
stream(request, context) {
|
|
9
|
+
return context.invokeStream(request, { target });
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendInstructions,
|
|
3
|
+
contentText,
|
|
4
|
+
insertBefore,
|
|
5
|
+
messageText,
|
|
6
|
+
messages,
|
|
7
|
+
removeRange,
|
|
8
|
+
type Program,
|
|
9
|
+
} from "@neutrome/lil-engine";
|
|
10
|
+
import { Opcode } from "@neutrome/lil-engine";
|
|
11
|
+
import {
|
|
12
|
+
appendToolInteraction,
|
|
13
|
+
makeMessage,
|
|
14
|
+
prependSystemPrompt,
|
|
15
|
+
} from "../synthetic/index.ts";
|
|
16
|
+
import type { ExecutorContext } from "../types.ts";
|
|
17
|
+
|
|
18
|
+
export const INTERNAL_DRAFT_TOOL_NAME = "knowledge";
|
|
19
|
+
export const INTERNAL_DRAFT_CALL_ID = "knowledge_0";
|
|
20
|
+
|
|
21
|
+
export type InternalDraft =
|
|
22
|
+
| string
|
|
23
|
+
| readonly string[]
|
|
24
|
+
| readonly { text: string }[];
|
|
25
|
+
|
|
26
|
+
export type TwoPassSettings = {
|
|
27
|
+
reasoningLevel?: string;
|
|
28
|
+
systemPrompt?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type TwoPassSettingsResolver = (
|
|
32
|
+
request: Program,
|
|
33
|
+
ctx: ExecutorContext,
|
|
34
|
+
) => TwoPassSettings | null | Promise<TwoPassSettings | null>;
|
|
35
|
+
|
|
36
|
+
export type TwoPassRequestOptions = {
|
|
37
|
+
maxTotalContextLength?: number;
|
|
38
|
+
buildFinalRequest?: (request: Program, draft: string) => Program;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function appendInternalDraft(
|
|
42
|
+
request: Program,
|
|
43
|
+
draft: InternalDraft,
|
|
44
|
+
options: { callId?: string } = {},
|
|
45
|
+
): Program {
|
|
46
|
+
const callId = options.callId ?? INTERNAL_DRAFT_CALL_ID;
|
|
47
|
+
const drafts = normalizeDrafts(draft);
|
|
48
|
+
if (drafts.length === 0) return request;
|
|
49
|
+
return drafts.reduce(
|
|
50
|
+
(program, text, index) =>
|
|
51
|
+
appendToolInteraction(program, {
|
|
52
|
+
callId: drafts.length === 1 ? callId : `${callId}_${index}`,
|
|
53
|
+
name: INTERNAL_DRAFT_TOOL_NAME,
|
|
54
|
+
args: {},
|
|
55
|
+
result: text,
|
|
56
|
+
}),
|
|
57
|
+
request,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildDraftRequest(
|
|
62
|
+
request: Program,
|
|
63
|
+
settings: TwoPassSettings,
|
|
64
|
+
): Program {
|
|
65
|
+
const withPrompt = settings.systemPrompt
|
|
66
|
+
? replaceSystemPrompt(request, settings.systemPrompt)
|
|
67
|
+
: request;
|
|
68
|
+
return applySettings(withPrompt, settings);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildFinalRequest(
|
|
72
|
+
options: TwoPassRequestOptions,
|
|
73
|
+
request: Program,
|
|
74
|
+
settings: TwoPassSettings,
|
|
75
|
+
draft: string,
|
|
76
|
+
): Program {
|
|
77
|
+
const limitedDraft = limitDraft(options, request, settings, draft);
|
|
78
|
+
const withDraft = options.buildFinalRequest
|
|
79
|
+
? options.buildFinalRequest(request, limitedDraft)
|
|
80
|
+
: appendInternalDraft(request, limitedDraft);
|
|
81
|
+
const withPrompt = settings.systemPrompt
|
|
82
|
+
? prependSystemPrompt(withDraft, settings.systemPrompt)
|
|
83
|
+
: withDraft;
|
|
84
|
+
return applySettings(withPrompt, settings);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function validateMaxContextLength(value: number | undefined): void {
|
|
88
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
|
|
89
|
+
throw new Error("maxTotalContextLength must be a non-negative integer");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function replaceSystemPrompt(request: Program, systemPrompt: string): Program {
|
|
94
|
+
const systemSpans = messages(request).filter(
|
|
95
|
+
(span) => span.role === "system",
|
|
96
|
+
);
|
|
97
|
+
const previousSystemPrompt = systemSpans
|
|
98
|
+
.map((span) => messageText(request, span).trim())
|
|
99
|
+
.filter(Boolean)
|
|
100
|
+
.join("\n\n");
|
|
101
|
+
let draft = request;
|
|
102
|
+
for (const span of systemSpans.slice().reverse()) {
|
|
103
|
+
draft = removeRange(draft, span.start, span.end);
|
|
104
|
+
}
|
|
105
|
+
draft = prependSystemPrompt(draft, systemPrompt);
|
|
106
|
+
if (!previousSystemPrompt) return draft;
|
|
107
|
+
const firstNonSystem = messages(draft).find((span) => span.role !== "system");
|
|
108
|
+
return insertBefore(
|
|
109
|
+
draft,
|
|
110
|
+
firstNonSystem?.start ?? draft.code.length,
|
|
111
|
+
makeMessage("user", previousSystemPrompt).code,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function applySettings(request: Program, settings: TwoPassSettings): Program {
|
|
116
|
+
if (!settings.reasoningLevel) return request;
|
|
117
|
+
return appendInstructions(
|
|
118
|
+
{
|
|
119
|
+
...request,
|
|
120
|
+
code: request.code.filter(
|
|
121
|
+
(instruction) => instruction.opcode !== Opcode.SET_REASON_EFFORT,
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
opcode: Opcode.SET_REASON_EFFORT,
|
|
127
|
+
value: { kind: "string", value: settings.reasoningLevel },
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function limitDraft(
|
|
134
|
+
options: TwoPassRequestOptions,
|
|
135
|
+
request: Program,
|
|
136
|
+
settings: TwoPassSettings,
|
|
137
|
+
draft: string,
|
|
138
|
+
): string {
|
|
139
|
+
if (options.maxTotalContextLength === undefined) return draft;
|
|
140
|
+
const base = buildFinalRequestWithoutDraft(options, request, settings);
|
|
141
|
+
const available = options.maxTotalContextLength - contentText(base).length;
|
|
142
|
+
return available > 0 ? draft.slice(0, available) : "";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function buildFinalRequestWithoutDraft(
|
|
146
|
+
options: TwoPassRequestOptions,
|
|
147
|
+
request: Program,
|
|
148
|
+
settings: TwoPassSettings,
|
|
149
|
+
): Program {
|
|
150
|
+
const withDraft = options.buildFinalRequest
|
|
151
|
+
? options.buildFinalRequest(request, "")
|
|
152
|
+
: request;
|
|
153
|
+
const withPrompt = settings.systemPrompt
|
|
154
|
+
? prependSystemPrompt(withDraft, settings.systemPrompt)
|
|
155
|
+
: withDraft;
|
|
156
|
+
return applySettings(withPrompt, settings);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeDrafts(draft: InternalDraft): string[] {
|
|
160
|
+
const drafts = Array.isArray(draft)
|
|
161
|
+
? draft.map((item) => (typeof item === "string" ? item : item.text))
|
|
162
|
+
: [draft];
|
|
163
|
+
return drafts.map((text) => text.trim()).filter((text) => text.length > 0);
|
|
164
|
+
}
|