@neutrome/lilsdk 0.4.6 → 0.5.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.
@@ -1,14 +1,17 @@
1
1
  import {
2
- contentText,
2
+ appendAssistantMessage,
3
+ appendToolInteraction,
3
4
  createProgram,
4
- emitChatCompletionsRequest,
5
- emitChatCompletionsStreamChunk,
6
- parseChatCompletionsRequest,
7
- parseChatCompletionsStreamChunk,
8
- programAttachments,
9
- viewProgram,
10
- setModel,
5
+ delta,
6
+ emitProviderRequest,
7
+ emitProviderStreamChunk,
8
+ extractAttachments,
9
+ extractContentText,
11
10
  Opcode,
11
+ parseProviderRequest,
12
+ parseProviderStreamChunk,
13
+ ProgramView,
14
+ setModel,
12
15
  type Program,
13
16
  } from "@neutrome/lil-engine";
14
17
  import { describe, expect, it } from "vitest";
@@ -21,15 +24,7 @@ import {
21
24
  map,
22
25
  reduce,
23
26
  } from "../src/primitives/index.ts";
24
- import {
25
- appendAssistantMessage,
26
- appendToolInteraction,
27
- } from "../src/synthetic/index.ts";
28
- import {
29
- observeExecutionStream,
30
- streamTextResponse,
31
- writeReasoning,
32
- } from "../src/stream/index.ts";
27
+ import { observeExecutionStream, writeThinking } from "../src/stream/index.ts";
33
28
  import { createGoalExecutor, fallback, retry } from "../src/loops/index.ts";
34
29
  import { createAttachmentToTextExecutor } from "../src/managed/attachment-to-text.ts";
35
30
  import { createCapabilitiesExecutor } from "../src/managed/capabilities.ts";
@@ -40,7 +35,8 @@ const decoder = new TextDecoder();
40
35
 
41
36
  describe("@neutrome/lilsdk", () => {
42
37
  it("leaves capability tool prefixes directly available", async () => {
43
- const request = parseChatCompletionsRequest(
38
+ const request = parseProviderRequest(
39
+ "chat-completions",
44
40
  encoder.encode(
45
41
  JSON.stringify({
46
42
  tools: [
@@ -61,14 +57,14 @@ describe("@neutrome/lilsdk", () => {
61
57
 
62
58
  await executor.execute(request, buildExecutorContext());
63
59
 
64
- expect(viewProgram(innerRequest!).tools.map((tool) => tool.name)).toEqual([
65
- "keep_search",
66
- "learn_capability",
67
- ]);
60
+ expect(
61
+ new ProgramView(innerRequest!).tools.map((tool) => tool.name),
62
+ ).toEqual(["keep_search", "learn_capability"]);
68
63
  });
69
64
 
70
65
  it("describes the latest attachment and removes it before the inner executor", async () => {
71
- const request = parseChatCompletionsRequest(
66
+ const request = parseProviderRequest(
67
+ "chat-completions",
72
68
  encoder.encode(
73
69
  JSON.stringify({
74
70
  messages: [
@@ -113,9 +109,9 @@ describe("@neutrome/lilsdk", () => {
113
109
  ],
114
110
  );
115
111
  await executor.execute(request, buildExecutorContext());
116
- expect(programAttachments(innerRequest!)).toHaveLength(0);
112
+ expect(extractAttachments(innerRequest!)).toHaveLength(0);
117
113
  expect(
118
- viewProgram(innerRequest!).messages.at(-1)?.toolResult?.text,
114
+ new ProgramView(innerRequest!).messages.at(-1)?.toolResult?.text,
119
115
  ).toContain("The original binary media is not present in this context.");
120
116
  });
121
117
  it("provides clone-safe structural primitives", () => {
@@ -150,7 +146,8 @@ describe("@neutrome/lilsdk", () => {
150
146
  it("observes tool-call streams", async () => {
151
147
  const observed = await observeExecutionStream(
152
148
  (async function* () {
153
- yield parseChatCompletionsStreamChunk(
149
+ yield parseProviderStreamChunk(
150
+ "chat-completions",
154
151
  encoder.encode(
155
152
  JSON.stringify({
156
153
  id: "tool-stream",
@@ -160,7 +157,8 @@ describe("@neutrome/lilsdk", () => {
160
157
  }),
161
158
  ),
162
159
  );
163
- yield parseChatCompletionsStreamChunk(
160
+ yield parseProviderStreamChunk(
161
+ "chat-completions",
164
162
  encoder.encode(
165
163
  JSON.stringify({
166
164
  id: "tool-stream",
@@ -184,7 +182,8 @@ describe("@neutrome/lilsdk", () => {
184
182
  }),
185
183
  ),
186
184
  );
187
- yield parseChatCompletionsStreamChunk(
185
+ yield parseProviderStreamChunk(
186
+ "chat-completions",
188
187
  encoder.encode(
189
188
  JSON.stringify({
190
189
  id: "tool-stream",
@@ -203,16 +202,18 @@ describe("@neutrome/lilsdk", () => {
203
202
  }
204
203
 
205
204
  const parsed = observed.chunks.map((chunk) =>
206
- JSON.parse(decoder.decode(emitChatCompletionsStreamChunk(chunk))),
205
+ JSON.parse(
206
+ decoder.decode(emitProviderStreamChunk("chat-completions", chunk)),
207
+ ),
207
208
  );
208
209
  expect(parsed.some((chunk) => chunk.choices?.[0]?.delta?.tool_calls)).toBe(
209
210
  true,
210
211
  );
211
212
  });
212
213
 
213
-
214
214
  it("appends generic synthetic tool interactions", () => {
215
- const request = parseChatCompletionsRequest(
215
+ const request = parseProviderRequest(
216
+ "chat-completions",
216
217
  encoder.encode(
217
218
  JSON.stringify({
218
219
  model: "virtual-model",
@@ -228,7 +229,7 @@ describe("@neutrome/lilsdk", () => {
228
229
  result: { status: "active" },
229
230
  });
230
231
  const emitted = JSON.parse(
231
- decoder.decode(emitChatCompletionsRequest(updated)),
232
+ decoder.decode(emitProviderRequest("chat-completions", updated)),
232
233
  );
233
234
 
234
235
  expect(emitted.messages[1].tool_calls[0].function).toEqual({
@@ -242,19 +243,20 @@ describe("@neutrome/lilsdk", () => {
242
243
  });
243
244
  });
244
245
 
245
-
246
246
  it("writes reasoning helpers to a sink", async () => {
247
247
  const emitted: string[] = [];
248
248
  const sink: OutputSink = {
249
249
  write(chunk) {
250
- emitted.push(decoder.decode(emitChatCompletionsStreamChunk(chunk)));
250
+ emitted.push(
251
+ decoder.decode(emitProviderStreamChunk("chat-completions", chunk)),
252
+ );
251
253
  },
252
254
  close() {
253
255
  emitted.push("[DONE]");
254
256
  },
255
257
  };
256
258
 
257
- await writeReasoning(sink, "thinking");
259
+ await writeThinking(sink, "thinking");
258
260
 
259
261
  expect(JSON.parse(emitted[0]!).choices[0].delta.reasoning_content).toBe(
260
262
  "thinking",
@@ -279,7 +281,7 @@ describe("@neutrome/lilsdk", () => {
279
281
  );
280
282
 
281
283
  expect(calls).toBe(2);
282
- expect(contentText(result)).toBe("ok");
284
+ expect(extractContentText(result)).toBe("ok");
283
285
  });
284
286
 
285
287
  it("falls back to the next executor after failure", async () => {
@@ -297,7 +299,7 @@ describe("@neutrome/lilsdk", () => {
297
299
  buildExecutorContext(),
298
300
  );
299
301
 
300
- expect(contentText(result)).toBe("fallback");
302
+ expect(extractContentText(result)).toBe("fallback");
301
303
  });
302
304
 
303
305
  it("does not retry a stream after exposing a partial chunk", async () => {
@@ -358,7 +360,7 @@ describe("@neutrome/lilsdk", () => {
358
360
  ),
359
361
  ),
360
362
  satisfied(review) {
361
- return contentText(review) === "pass";
363
+ return extractContentText(review) === "pass";
362
364
  },
363
365
  refine(request) {
364
366
  return request;
@@ -371,7 +373,7 @@ describe("@neutrome/lilsdk", () => {
371
373
  );
372
374
 
373
375
  expect(attempts).toBe(2);
374
- expect(contentText(result)).toBe("answer 2");
376
+ expect(extractContentText(result)).toBe("answer 2");
375
377
  });
376
378
 
377
379
  it("streams draft and review passes through each goal iteration", async () => {
@@ -391,7 +393,7 @@ describe("@neutrome/lilsdk", () => {
391
393
  );
392
394
  }),
393
395
  satisfied(review) {
394
- return contentText(review) === "pass";
396
+ return extractContentText(review) === "pass";
395
397
  },
396
398
  refine(request) {
397
399
  return request;
@@ -406,7 +408,7 @@ describe("@neutrome/lilsdk", () => {
406
408
  chunks.push(chunk);
407
409
  }
408
410
 
409
- expect(chunks.map(contentText).filter(Boolean)).toEqual([
411
+ expect(chunks.map(extractContentText).filter(Boolean)).toEqual([
410
412
  "answer 1",
411
413
  "retry",
412
414
  "answer 2",
@@ -473,14 +475,14 @@ describe("@neutrome/lilsdk", () => {
473
475
  let reviewedAnswer = "";
474
476
  const executor = createGoalExecutor({
475
477
  draft: streamingExecutor(async function* () {
476
- yield* streamTextResponse("answer");
478
+ yield* delta.textResponse("answer");
477
479
  }),
478
480
  review: streamingExecutor(async function* (request) {
479
- reviewedAnswer = contentText(request);
480
- yield* streamTextResponse("pass");
481
+ reviewedAnswer = extractContentText(request);
482
+ yield* delta.textResponse("pass");
481
483
  }),
482
484
  satisfied(review) {
483
- return contentText(review) === "pass";
485
+ return extractContentText(review) === "pass";
484
486
  },
485
487
  refine(request) {
486
488
  return request;
@@ -1,13 +1,13 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
- callData,
4
3
  createProgram,
4
+ extractToolCalls,
5
5
  Opcode,
6
+ ProgramView,
6
7
  type Instruction,
7
8
  type Program,
8
- viewProgram,
9
9
  } from "@neutrome/lil-engine";
10
- import { connectTools } from "../src/tools.ts";
10
+ import { createToolsExecutor } from "../src/tools.ts";
11
11
  import type { ExecutorContext, Tool } from "../src/types.ts";
12
12
 
13
13
  function buildToolCallResponse(
@@ -64,7 +64,7 @@ const calculatorTool: Tool = {
64
64
  },
65
65
  };
66
66
 
67
- describe("connectTools", () => {
67
+ describe("createToolsExecutor", () => {
68
68
  it("passes the executor cache to connected tools", async () => {
69
69
  let callCount = 0;
70
70
  const cache = {
@@ -83,7 +83,7 @@ describe("connectTools", () => {
83
83
  return (await ctx.cache.get("tool-key")) ?? "missing";
84
84
  },
85
85
  };
86
- const executor = connectTools([tool], "test-model");
86
+ const executor = createToolsExecutor("test-model", [tool]);
87
87
  const ctx = buildCtx({
88
88
  async invoke() {
89
89
  callCount += 1;
@@ -105,7 +105,7 @@ describe("connectTools", () => {
105
105
  });
106
106
 
107
107
  it("passes through when LLM returns no tool calls", async () => {
108
- const executor = connectTools([calculatorTool], "test-model");
108
+ const executor = createToolsExecutor("test-model", [calculatorTool]);
109
109
 
110
110
  const request = createProgram({
111
111
  code: [
@@ -136,7 +136,7 @@ describe("connectTools", () => {
136
136
  });
137
137
 
138
138
  it("executes connected tool and loops until text response", async () => {
139
- const executor = connectTools([calculatorTool], "test-model");
139
+ const executor = createToolsExecutor("test-model", [calculatorTool]);
140
140
 
141
141
  let callCount = 0;
142
142
  const request = createProgram({
@@ -175,7 +175,7 @@ describe("connectTools", () => {
175
175
  });
176
176
 
177
177
  it("returns malformed connected-tool arguments to the model", async () => {
178
- const executor = connectTools([calculatorTool], "test-model");
178
+ const executor = createToolsExecutor("test-model", [calculatorTool]);
179
179
  let retryRequest: Program | undefined;
180
180
  let invocations = 0;
181
181
  const ctx = buildCtx({
@@ -198,9 +198,11 @@ describe("connectTools", () => {
198
198
 
199
199
  expect(invocations).toBe(2);
200
200
  expect(
201
- viewProgram(retryRequest!).messages.at(-1)?.toolResult?.text,
202
- ).toBe('Tool "calculator" failed: Tool "calculator" received invalid JSON object arguments');
203
- expect(callData(result)).toHaveLength(0);
201
+ new ProgramView(retryRequest!).messages.at(-1)?.toolResult?.text,
202
+ ).toBe(
203
+ 'Tool "calculator" failed: Tool "calculator" received invalid JSON object arguments',
204
+ );
205
+ expect(extractToolCalls(result)).toHaveLength(0);
204
206
  });
205
207
 
206
208
  it("returns thrown connected-tool errors to the model", async () => {
@@ -210,7 +212,7 @@ describe("connectTools", () => {
210
212
  throw new Error("Calculator service is unavailable");
211
213
  },
212
214
  };
213
- const executor = connectTools([failingTool], "test-model");
215
+ const executor = createToolsExecutor("test-model", [failingTool]);
214
216
  let retryRequest: Program | undefined;
215
217
  let invocations = 0;
216
218
  const ctx = buildCtx({
@@ -233,7 +235,7 @@ describe("connectTools", () => {
233
235
 
234
236
  expect(invocations).toBe(2);
235
237
  expect(
236
- viewProgram(retryRequest!).messages.at(-1)?.toolResult?.text,
238
+ new ProgramView(retryRequest!).messages.at(-1)?.toolResult?.text,
237
239
  ).toBe('Tool "calculator" failed: Calculator service is unavailable');
238
240
  });
239
241
 
@@ -249,7 +251,7 @@ describe("connectTools", () => {
249
251
  return `value_of_${key}`;
250
252
  },
251
253
  };
252
- const executor = connectTools([multiTool], "test-model");
254
+ const executor = createToolsExecutor("test-model", [multiTool]);
253
255
 
254
256
  let callCount = 0;
255
257
  const request = createProgram();
@@ -276,7 +278,7 @@ describe("connectTools", () => {
276
278
  });
277
279
 
278
280
  it("returns immediately when outer (non-connected) tool calls are present", async () => {
279
- const executor = connectTools([calculatorTool], "test-model");
281
+ const executor = createToolsExecutor("test-model", [calculatorTool]);
280
282
 
281
283
  const request = createProgram();
282
284
  const ctx = buildCtx({
@@ -298,17 +300,17 @@ describe("connectTools", () => {
298
300
  return calculatorTool.execute(args, ctx);
299
301
  },
300
302
  };
301
- const guardedExecutor = connectTools([tool], "test-model");
303
+ const guardedExecutor = createToolsExecutor("test-model", [tool]);
302
304
  const result = await guardedExecutor.execute(request, ctx);
303
305
 
304
- const calls = callData(result);
306
+ const calls = extractToolCalls(result);
305
307
  expect(calls).toHaveLength(2);
306
308
  expect(calls.some((c) => c.name === "external_tool")).toBe(true);
307
309
  expect(executions).toBe(0);
308
310
  });
309
311
 
310
312
  it("respects maxIterations", async () => {
311
- const executor = connectTools([calculatorTool], "test-model", {
313
+ const executor = createToolsExecutor("test-model", [calculatorTool], {
312
314
  maxIterations: 2,
313
315
  });
314
316
 
@@ -336,7 +338,7 @@ describe("connectTools", () => {
336
338
  });
337
339
 
338
340
  it("streams final response after tool loop completes", async () => {
339
- const executor = connectTools([calculatorTool], "test-model");
341
+ const executor = createToolsExecutor("test-model", [calculatorTool]);
340
342
 
341
343
  let callCount = 0;
342
344
  const request = createProgram();
@@ -395,7 +397,7 @@ describe("connectTools", () => {
395
397
  return "4";
396
398
  },
397
399
  };
398
- const executor = connectTools([tool], "test-model");
400
+ const executor = createToolsExecutor("test-model", [tool]);
399
401
  let invocation = 0;
400
402
  const ctx = buildCtx({
401
403
  async invoke() {
@@ -1,46 +0,0 @@
1
- ## API Report File for "neutrome"
2
-
3
- > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4
-
5
- ```ts
6
-
7
- import { Program } from '@neutrome/lil-engine';
8
-
9
- // @public (undocumented)
10
- export function appendAssistantMessage(program: Program, text: string): Program;
11
-
12
- // @public (undocumented)
13
- export function appendToolInteraction(program: Program, interaction: ToolInteraction): Program;
14
-
15
- // @public (undocumented)
16
- export function appendUserMessage(program: Program, text: string): Program;
17
-
18
- // @public (undocumented)
19
- export function createModelProgram(model: string): Program;
20
-
21
- // @public (undocumented)
22
- export function makeMessage(role: TextMessageRole, text: string): Program;
23
-
24
- // @public (undocumented)
25
- export function makeToolCall(name: string, args?: unknown, callId?: string): Program;
26
-
27
- // @public (undocumented)
28
- export function makeToolResponse(callId: string, result: unknown): Program;
29
-
30
- // @public (undocumented)
31
- export function prependSystemPrompt(program: Program, text: string): Program;
32
-
33
- // @public (undocumented)
34
- export type TextMessageRole = "system" | "user" | "assistant";
35
-
36
- // @public (undocumented)
37
- export type ToolInteraction = {
38
- callId: string;
39
- name: string;
40
- args?: unknown;
41
- result: unknown;
42
- };
43
-
44
- // (No @packageDocumentation comment for this package)
45
-
46
- ```
@@ -1,103 +0,0 @@
1
- ## API Report File for "neutrome"
2
-
3
- > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4
-
5
- ```ts
6
-
7
- import type { Program } from '@neutrome/lil-engine';
8
-
9
- // @public (undocumented)
10
- type Cache_2 = {
11
- get(key: string): Promise<string | null>;
12
- put(key: string, value: string, options?: CachePutOptions): Promise<void>;
13
- delete(key: string): Promise<void>;
14
- };
15
- export { Cache_2 as Cache }
16
-
17
- // @public (undocumented)
18
- export type CachePutOptions = {
19
- expiration?: number;
20
- expirationTtl?: number;
21
- };
22
-
23
- // @public (undocumented)
24
- export function createExecutionEvent(input: ExecutionEventInput): ExecutionEvent;
25
-
26
- // @public (undocumented)
27
- export type ExecutionEvent = {
28
- kind: string;
29
- requestId: string;
30
- executionId: string;
31
- timestamp: string;
32
- parentExecutionId?: string;
33
- target?: {
34
- kind: "provider" | "executor";
35
- id: string;
36
- };
37
- errorKind?: string;
38
- data?: Record<string, unknown>;
39
- };
40
-
41
- // @public (undocumented)
42
- export type ExecutionEventInput = Omit<ExecutionEvent, "timestamp"> & {
43
- timestamp?: string;
44
- };
45
-
46
- // @public (undocumented)
47
- export type Executor = {
48
- execute(request: Program, ctx: ExecutorContext): Promise<Program>;
49
- stream(request: Program, ctx: ExecutorContext): AsyncIterable<Program>;
50
- };
51
-
52
- // Warning: (ae-forgotten-export) The symbol "RuntimeContext" needs to be exported by the entry point types.d.ts
53
- //
54
- // @public (undocumented)
55
- export type ExecutorContext = RuntimeContext & {
56
- requestId: string;
57
- executionId: string;
58
- parentExecutionId?: string;
59
- };
60
-
61
- // @public (undocumented)
62
- export type ExecutorInput = string | Executor;
63
-
64
- // @public (undocumented)
65
- export type OutputSink = {
66
- write(chunk: Program): void | Promise<void>;
67
- close(): void | Promise<void>;
68
- };
69
-
70
- // @public (undocumented)
71
- export type ProgramTransform = {
72
- name: string;
73
- capabilities: TransformCapability[];
74
- apply(program: Program, ctx: TransformContext): Program | Promise<Program>;
75
- };
76
-
77
- // @public (undocumented)
78
- type RuntimeContext = {
79
- cache: Cache_2;
80
- invoke(executor: ExecutorInput, request: Program): Promise<Program>;
81
- invokeStream(executor: ExecutorInput, request: Program): AsyncIterable<Program>;
82
- observe(event: ExecutionEvent): void;
83
- signal: AbortSignal;
84
- };
85
-
86
- // @public (undocumented)
87
- export type Tool = {
88
- name: string;
89
- description: string;
90
- schema: Record<string, unknown>;
91
- systemPromptFragment?: string;
92
- execute(args: Record<string, unknown>, ctx: ExecutorContext): Promise<string>;
93
- };
94
-
95
- // @public (undocumented)
96
- export type TransformCapability = "read_messages" | "write_messages" | "write_config" | "write_tools" | "drop_content" | "provider_extension";
97
-
98
- // @public (undocumented)
99
- export type TransformContext = RuntimeContext;
100
-
101
- // (No @packageDocumentation comment for this package)
102
-
103
- ```
@@ -1,134 +0,0 @@
1
- import {
2
- addTool,
3
- appendInstructions,
4
- createProgram,
5
- insertBefore,
6
- messages,
7
- Opcode,
8
- setModel,
9
- type Instruction,
10
- type Opcode as OpcodeValue,
11
- type Program,
12
- } from "@neutrome/lil-engine";
13
-
14
- const encoder = new TextEncoder();
15
-
16
- export type TextMessageRole = "system" | "user" | "assistant";
17
-
18
- export type ToolInteraction = {
19
- callId: string;
20
- name: string;
21
- args?: unknown;
22
- result: unknown;
23
- };
24
-
25
- export function createModelProgram(model: string): Program {
26
- return setModel(createProgram(), model);
27
- }
28
-
29
- export function makeMessage(role: TextMessageRole, text: string): Program {
30
- return createProgram({ code: buildMessage(role, text) });
31
- }
32
-
33
- export function prependSystemPrompt(program: Program, text: string): Program {
34
- const systemMessage = buildMessage("system", text);
35
- const spans = messages(program);
36
- if (spans.length === 0) {
37
- return appendInstructions(program, systemMessage);
38
- }
39
- const firstSpan = spans[0];
40
- if (!firstSpan) return appendInstructions(program, systemMessage);
41
- return insertBefore(program, firstSpan.start, systemMessage);
42
- }
43
-
44
- export function appendUserMessage(program: Program, text: string): Program {
45
- return appendInstructions(program, buildMessage("user", text));
46
- }
47
-
48
- export function appendAssistantMessage(
49
- program: Program,
50
- text: string,
51
- ): Program {
52
- return appendInstructions(program, buildMessage("assistant", text));
53
- }
54
-
55
- export function makeToolCall(
56
- name: string,
57
- args: unknown = {},
58
- callId: string = crypto.randomUUID(),
59
- ): Program {
60
- return createProgram({
61
- code: [
62
- { opcode: Opcode.MSG_START, value: { kind: "none" } },
63
- { opcode: Opcode.ROLE_AST, value: { kind: "none" } },
64
- { opcode: Opcode.CALL_START, value: { kind: "string", value: callId } },
65
- { opcode: Opcode.CALL_NAME, value: { kind: "string", value: name } },
66
- {
67
- opcode: Opcode.CALL_ARGS,
68
- value: { kind: "json", value: encodeJson(args) },
69
- },
70
- { opcode: Opcode.CALL_END, value: { kind: "none" } },
71
- { opcode: Opcode.MSG_END, value: { kind: "none" } },
72
- ],
73
- });
74
- }
75
-
76
- export function makeToolResponse(callId: string, result: unknown): Program {
77
- return createProgram({ code: buildToolResponse(callId, result) });
78
- }
79
-
80
- export function appendToolInteraction(
81
- program: Program,
82
- interaction: ToolInteraction,
83
- ): Program {
84
- return appendInstructions(program, [
85
- ...makeToolCall(
86
- interaction.name,
87
- interaction.args ?? {},
88
- interaction.callId,
89
- ).code,
90
- ...buildToolResponse(interaction.callId, interaction.result),
91
- ]);
92
- }
93
-
94
- function buildMessage(role: TextMessageRole, text: string): Instruction[] {
95
- return [
96
- { opcode: Opcode.MSG_START, value: { kind: "none" } },
97
- { opcode: roleOpcode(role), value: { kind: "none" } },
98
- { opcode: Opcode.TXT_CHUNK, value: { kind: "string", value: text } },
99
- { opcode: Opcode.MSG_END, value: { kind: "none" } },
100
- ];
101
- }
102
-
103
- function buildToolResponse(callId: string, result: unknown): Instruction[] {
104
- return [
105
- { opcode: Opcode.MSG_START, value: { kind: "none" } },
106
- { opcode: Opcode.ROLE_TOOL, value: { kind: "none" } },
107
- { opcode: Opcode.RESULT_START, value: { kind: "string", value: callId } },
108
- {
109
- opcode: Opcode.RESULT_DATA,
110
- value: { kind: "string", value: stringifyResult(result) },
111
- },
112
- { opcode: Opcode.RESULT_END, value: { kind: "none" } },
113
- { opcode: Opcode.MSG_END, value: { kind: "none" } },
114
- ];
115
- }
116
-
117
- function roleOpcode(role: TextMessageRole): OpcodeValue {
118
- switch (role) {
119
- case "system":
120
- return Opcode.ROLE_SYS;
121
- case "user":
122
- return Opcode.ROLE_USR;
123
- case "assistant":
124
- return Opcode.ROLE_AST;
125
- }
126
- }
127
-
128
- function encodeJson(value: unknown): Uint8Array {
129
- return encoder.encode(JSON.stringify(value));
130
- }
131
-
132
- function stringifyResult(result: unknown): string {
133
- return typeof result === "string" ? result : JSON.stringify(result);
134
- }