@deepstrike/sdk 0.1.11 → 0.1.12

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 CHANGED
@@ -50,6 +50,15 @@ const result = await agent.run("What is 17 + 28?")
50
50
  console.log(result)
51
51
  ```
52
52
 
53
+ Same-session conversation continuity is explicit via `sessionId`:
54
+
55
+ ```typescript
56
+ await agent.run("My name is Ada.", undefined, undefined, "chat-1")
57
+ const reply = await agent.run("What is my name?", undefined, undefined, "chat-1")
58
+ ```
59
+
60
+ By default, the agent keeps session transcripts in memory for the lifetime of that `Agent` instance. Provide a `sessionStore` when the transcript must survive process restarts or be shared across workers.
61
+
53
62
  Streaming:
54
63
 
55
64
  ```typescript
@@ -196,6 +205,22 @@ const agent = new Agent(provider, {
196
205
  const result = await agent.dream("my-agent", Date.now())
197
206
  ```
198
207
 
208
+ ### SessionStore (same-session transcript continuity)
209
+
210
+ ```typescript
211
+ import type { SessionStore } from "@deepstrike/sdk"
212
+
213
+ class MySessionStore implements SessionStore {
214
+ async loadSession(sessionId) { return db.sessions.get(sessionId) }
215
+ async saveSession(session) { await db.sessions.put(session.sessionId, session) }
216
+ }
217
+
218
+ const agent = new Agent(provider, {
219
+ maxTokens: 4096,
220
+ sessionStore: new MySessionStore(),
221
+ })
222
+ ```
223
+
199
224
  ---
200
225
 
201
226
  ## Governance
package/dist/agent.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { LLMProvider, StreamEvent } from "./types.js";
2
2
  import type { RegisteredTool } from "./tools/index.js";
3
- import type { DreamStore, DreamResult } from "./memory/protocols.js";
3
+ import type { DreamStore, DreamResult, SessionStore } from "./memory/protocols.js";
4
4
  import type { KnowledgeSource } from "./knowledge/source.js";
5
5
  import type { SignalSource } from "./signals/types.js";
6
6
  export interface AgentOptions {
@@ -13,12 +13,6 @@ export interface AgentOptions {
13
13
  * Passed to the kernel's `system` partition before the first LLM call.
14
14
  */
15
15
  systemPrompt?: string;
16
- /**
17
- * Long-term memory snippets pre-seeded into the context before the first LLM call.
18
- * Each string is pushed to the kernel's `memory` partition (highest-priority context
19
- * after system). Use to inject memories retrieved from a DreamStore before a run.
20
- */
21
- initialMemory?: string[];
22
16
  /**
23
17
  * Directory containing skill `.md` files. The kernel auto-injects a `skill`
24
18
  * meta-tool so the model can load any skill by name on demand.
@@ -28,6 +22,8 @@ export interface AgentOptions {
28
22
  signalSource?: SignalSource;
29
23
  /** Backing store for the idle dreaming pipeline. Required to call `Agent.dream()`. */
30
24
  dreamStore?: DreamStore;
25
+ /** Optional durable transcript store for same-session conversational continuity. */
26
+ sessionStore?: SessionStore;
31
27
  /**
32
28
  * Stable identifier for this agent. Required to enable in-session memory retrieval
33
29
  * when `dreamStore` is configured.
@@ -56,6 +52,7 @@ export declare class Agent {
56
52
  private knowledgeSource?;
57
53
  private signalSource?;
58
54
  private dreamStore?;
55
+ private readonly inMemorySessions;
59
56
  private interrupted;
60
57
  private pendingInterrupt;
61
58
  private _turn;
@@ -72,8 +69,12 @@ export declare class Agent {
72
69
  * Collect the full text response and return it.
73
70
  * For richer control (streaming, tool events, token counts) use `runStreaming`.
74
71
  */
75
- run(goal: string, criteria?: string[], extensions?: Record<string, unknown>): Promise<string>;
76
- runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
72
+ run(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): Promise<string>;
73
+ runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): AsyncIterable<StreamEvent>;
74
+ private loadSession;
75
+ private saveSession;
76
+ private toMessage;
77
+ private kernelMsgToSessionMsg;
77
78
  /**
78
79
  * Trigger the idle dreaming cycle for this agent.
79
80
  * Requires `dreamStore` and `agentId` to be configured.
package/dist/agent.js CHANGED
@@ -10,6 +10,7 @@ export class Agent {
10
10
  knowledgeSource;
11
11
  signalSource;
12
12
  dreamStore;
13
+ inMemorySessions = new Map();
13
14
  interrupted = false;
14
15
  pendingInterrupt = false;
15
16
  // Live telemetry — updated each runStreaming call
@@ -44,15 +45,15 @@ export class Agent {
44
45
  * Collect the full text response and return it.
45
46
  * For richer control (streaming, tool events, token counts) use `runStreaming`.
46
47
  */
47
- async run(goal, criteria, extensions) {
48
+ async run(goal, criteria, extensions, sessionId) {
48
49
  let content = "";
49
- for await (const evt of this.runStreaming(goal, criteria, extensions)) {
50
+ for await (const evt of this.runStreaming(goal, criteria, extensions, sessionId)) {
50
51
  if (evt.type === "text_delta")
51
52
  content += evt.delta;
52
53
  }
53
54
  return content;
54
55
  }
55
- async *runStreaming(goal, criteria, extensions) {
56
+ async *runStreaming(goal, criteria, extensions, sessionId) {
56
57
  this.interrupted = false;
57
58
  this.pendingInterrupt = false;
58
59
  this._turn = 0;
@@ -76,8 +77,10 @@ export class Agent {
76
77
  const tokens = Math.max(1, Math.ceil(this.options.systemPrompt.length / 4));
77
78
  sm.addSystemMessage(this.options.systemPrompt, tokens);
78
79
  }
79
- for (const mem of this.options.initialMemory ?? []) {
80
- sm.addMemoryMessage(mem, Math.max(1, Math.ceil(mem.length / 4)));
80
+ const previousSession = sessionId ? await this.loadSession(sessionId) : undefined;
81
+ const previousMsgs = previousSession?.messages ?? [];
82
+ if (previousMsgs.length > 0) {
83
+ sm.preloadHistory(previousMsgs.map(m => this.toMessage(m)));
81
84
  }
82
85
  if (this.skillDir) {
83
86
  const skillMetas = await scanSkillDir(this.skillDir);
@@ -97,7 +100,6 @@ export class Agent {
97
100
  }
98
101
  let action = sm.start({ goal, criteria: criteria ?? [] });
99
102
  const sessionStart = Date.now();
100
- const sessionMsgs = [{ role: "user", content: goal }];
101
103
  while (!sm.isTerminal()) {
102
104
  // Update telemetry
103
105
  this._turn = sm.turn;
@@ -153,11 +155,11 @@ export class Agent {
153
155
  if (action.kind === "call_llm") {
154
156
  const finalToolCalls = [];
155
157
  let finalText = "";
156
- const messages = (action.messages ?? []);
158
+ const context = action.context;
157
159
  const tools = (action.tools ?? []);
158
160
  let turnTokens = 0;
159
161
  try {
160
- for await (const evt of this.provider.stream(messages, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
162
+ for await (const evt of this.provider.stream(context, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
161
163
  if (evt.type === "usage") {
162
164
  turnTokens = evt.totalTokens;
163
165
  continue;
@@ -177,7 +179,6 @@ export class Agent {
177
179
  break;
178
180
  }
179
181
  action = sm.feedLlmResponse({ role: "assistant", content: finalText, toolCalls: finalToolCalls, tokenCount: turnTokens || undefined });
180
- sessionMsgs.push({ role: "assistant", content: finalText, toolCalls: finalToolCalls });
181
182
  }
182
183
  else if (action.kind === "execute_tools") {
183
184
  const allCalls = action.calls ?? [];
@@ -266,12 +267,13 @@ export class Agent {
266
267
  this._pressure = sm.pressure();
267
268
  const status = result?.termination ?? "error";
268
269
  const iterations = result ? Math.max(1, result.turnsUsed) : 0;
269
- if (this.options.dreamStore && this.options.agentId && sessionMsgs.length > 1) {
270
+ const newMsgs = sm.drainNewMessages().map(m => this.kernelMsgToSessionMsg(m));
271
+ if (this.options.dreamStore && this.options.agentId && newMsgs.length > 0) {
270
272
  try {
271
273
  await this.options.dreamStore.saveSession({
272
274
  sessionId: crypto.randomUUID(),
273
275
  agentId: this.options.agentId,
274
- messages: sessionMsgs,
276
+ messages: newMsgs,
275
277
  metadata: null,
276
278
  createdAtMs: sessionStart,
277
279
  updatedAtMs: Date.now(),
@@ -279,6 +281,17 @@ export class Agent {
279
281
  }
280
282
  catch { /* session save failure must not surface to caller */ }
281
283
  }
284
+ if (sessionId) {
285
+ const now = Date.now();
286
+ await this.saveSession({
287
+ sessionId,
288
+ agentId: this.options.agentId ?? "default",
289
+ messages: [...previousMsgs, ...newMsgs],
290
+ metadata: previousSession?.metadata ?? null,
291
+ createdAtMs: previousSession?.createdAtMs ?? sessionStart,
292
+ updatedAtMs: now,
293
+ });
294
+ }
282
295
  yield {
283
296
  type: "done",
284
297
  iterations,
@@ -286,6 +299,36 @@ export class Agent {
286
299
  status,
287
300
  };
288
301
  }
302
+ async loadSession(sessionId) {
303
+ return this.options.sessionStore
304
+ ? this.options.sessionStore.loadSession(sessionId)
305
+ : this.inMemorySessions.get(sessionId);
306
+ }
307
+ async saveSession(data) {
308
+ if (this.options.sessionStore) {
309
+ await this.options.sessionStore.saveSession(data);
310
+ return;
311
+ }
312
+ this.inMemorySessions.set(data.sessionId, data);
313
+ }
314
+ toMessage(message) {
315
+ return {
316
+ role: message.role,
317
+ content: message.content,
318
+ contentParts: message.contentParts,
319
+ tokenCount: message.tokenCount,
320
+ toolCalls: message.toolCalls ?? [],
321
+ };
322
+ }
323
+ kernelMsgToSessionMsg(msg) {
324
+ return {
325
+ role: msg.role,
326
+ content: msg.content,
327
+ contentParts: msg.contentParts,
328
+ tokenCount: msg.tokenCount,
329
+ toolCalls: msg.toolCalls?.length ? msg.toolCalls : undefined,
330
+ };
331
+ }
289
332
  /**
290
333
  * Trigger the idle dreaming cycle for this agent.
291
334
  * Requires `dreamStore` and `agentId` to be configured.
@@ -332,7 +375,16 @@ export class Agent {
332
375
  }
333
376
  let synthesisText = "";
334
377
  const providerState = this.provider.createRunState?.();
335
- for await (const evt of this.provider.stream((action1.messages ?? []), [], undefined, providerState)) {
378
+ // IdlePipeline produces raw messages for synthesis; wrap them in a RenderedContext.
379
+ // The first system message (if any) becomes systemText; the rest are turns.
380
+ const synthMsgs = (action1.messages ?? []);
381
+ const synthSystemMsgs = synthMsgs.filter(m => m.role === "system");
382
+ const synthTurns = synthMsgs.filter(m => m.role !== "system");
383
+ const synthContext = {
384
+ systemText: synthSystemMsgs.map(m => m.content).join("\n\n"),
385
+ turns: synthTurns,
386
+ };
387
+ for await (const evt of this.provider.stream(synthContext, [], undefined, providerState)) {
336
388
  if (evt.type === "text_delta")
337
389
  synthesisText += evt.delta;
338
390
  }
@@ -92,7 +92,13 @@ export class HarnessLoop {
92
92
  if (evalAction.kind !== "evaluate")
93
93
  break;
94
94
  let evalText = "";
95
- for await (const evt of this.evalProvider.stream(evalAction.messages ?? [], [], undefined)) {
95
+ // Wrap harness eval messages in a RenderedContext (system messages → systemText, rest → turns).
96
+ const evalMsgs = evalAction.messages ?? [];
97
+ const evalContext = {
98
+ systemText: evalMsgs.filter((m) => m.role === "system").map((m) => m.content).join("\n\n"),
99
+ turns: evalMsgs.filter((m) => m.role !== "system"),
100
+ };
101
+ for await (const evt of this.evalProvider.stream(evalContext, [], undefined)) {
96
102
  if (evt.type === "text_delta")
97
103
  evalText += evt.delta;
98
104
  }
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export type { RegisteredTool } from "./tools/index.js";
20
20
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
21
21
  export type { SkillMetadata } from "./skills/loader.js";
22
22
  export { WorkingMemory } from "./memory/working.js";
23
- export type { DreamStore, DreamResult, SessionData, SessionMessage, MemoryEntry, CurationResult, CurationStats, } from "./memory/protocols.js";
23
+ export type { DreamStore, DreamResult, SessionStore, SessionData, SessionMessage, MemoryEntry, CurationResult, CurationStats, } from "./memory/protocols.js";
24
24
  export type { KnowledgeSource } from "./knowledge/source.js";
25
25
  export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
26
26
  export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions, QualityGate } from "./harness/harness.js";
@@ -31,7 +31,7 @@ export { PermissionManager, PermissionMode } from "./safety/permissions.js";
31
31
  export type { PermissionDecision, Permission } from "./safety/permissions.js";
32
32
  export { Governance } from "./governance.js";
33
33
  export type { GovernanceVerdict } from "./governance.js";
34
- export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, } from "./types.js";
34
+ export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, RenderedContext, } from "./types.js";
35
35
  export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
36
36
  export { ContractBuilder, formatContractForSystemPrompt, contractToCriteriaStrings, } from "./collaboration/contract.js";
37
37
  export { AgentPool } from "./collaboration/pool.js";
package/dist/kernel.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Message, ToolCall, ToolResult, ToolSchema } from "./types.js";
1
+ import type { Message, RenderedContext, ToolCall, ToolResult, ToolSchema } from "./types.js";
2
2
  import type { SkillMetadata } from "./skills/loader.js";
3
3
  export interface GovernanceVerdict {
4
4
  kind: "allow" | "deny" | "rate_limited" | "ask_user";
@@ -28,7 +28,7 @@ export interface RuntimeSignal {
28
28
  }
29
29
  export interface LoopAction {
30
30
  kind: "call_llm" | "execute_tools" | "done";
31
- messages?: Message[];
31
+ context?: RenderedContext;
32
32
  tools?: ToolSchema[];
33
33
  calls?: ToolCall[];
34
34
  result?: {
@@ -48,6 +48,7 @@ interface LoopStateMachineInstance {
48
48
  setKnowledgeEnabled(enabled: boolean): void;
49
49
  addSystemMessage(content: string, tokens: number): void;
50
50
  addMemoryMessage(content: string, tokens: number): void;
51
+ addHistoryMessage(message: Message, tokens: number): void;
51
52
  setTools(tools: ToolSchema[]): void;
52
53
  start(task: {
53
54
  goal: string;
@@ -57,6 +58,8 @@ interface LoopStateMachineInstance {
57
58
  feedToolResults(results: ToolResult[]): LoopAction;
58
59
  feedTimeout(): LoopAction;
59
60
  isTerminal(): boolean;
61
+ preloadHistory(messages: Message[]): void;
62
+ drainNewMessages(): Message[];
60
63
  readonly turn: number;
61
64
  pressure(): number;
62
65
  takeObservations(): LoopObservation[];
@@ -1,7 +1,9 @@
1
- import type { Message } from "../types.js";
1
+ import type { Message, ContentPart } from "../types.js";
2
2
  export interface SessionMessage {
3
3
  role: Message["role"];
4
4
  content: string;
5
+ /** Structured multimodal parts. Preserved for round-trip fidelity (e.g. tool result messages). */
6
+ contentParts?: ContentPart[];
5
7
  tokenCount?: number;
6
8
  toolCalls?: Array<{
7
9
  id: string;
@@ -43,6 +45,11 @@ export interface DreamStore {
43
45
  /** Persist a completed session for future consolidation via `Agent.dream()`. */
44
46
  saveSession(data: SessionData): Promise<void>;
45
47
  }
48
+ /** Durable transcript storage for same-session conversational continuity. */
49
+ export interface SessionStore {
50
+ loadSession(sessionId: string): Promise<SessionData | undefined>;
51
+ saveSession(data: SessionData): Promise<void>;
52
+ }
46
53
  export interface DreamResult {
47
54
  sessionsProcessed: number;
48
55
  insightsExtracted: number;
@@ -1,4 +1,4 @@
1
- import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
1
+ import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
2
2
  interface AnthropicProviderOptions {
3
3
  baseURL?: string;
4
4
  authMode?: "api-key" | "bearer";
@@ -15,8 +15,8 @@ export declare class AnthropicProvider implements LLMProvider {
15
15
  baseDelay: number;
16
16
  }, options?: AnthropicProviderOptions);
17
17
  private buildTools;
18
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
19
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
19
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
20
20
  private buildMessages;
21
21
  private rememberNativeBlocks;
22
22
  private assistantReplayKey;
@@ -1,6 +1,6 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker, normalizeToolCall, splitAnthropicSystem, toAnthropicMessages } from "./base.js";
3
+ import { CircuitBreaker, normalizeToolCall, toAnthropicMessages } from "./base.js";
4
4
  export class AnthropicProvider {
5
5
  model;
6
6
  client;
@@ -25,11 +25,11 @@ export class AnthropicProvider {
25
25
  input_schema: JSON.parse(t.parameters),
26
26
  }));
27
27
  }
28
- async complete(messages, tools) {
28
+ async complete(context, tools) {
29
29
  if (this.circuit.isOpen())
30
30
  throw new Error("Circuit breaker open");
31
- const system = splitAnthropicSystem(messages);
32
- const msgs = this.buildMessages(messages);
31
+ const system = context.systemText || undefined;
32
+ const msgs = this.buildMessages(context);
33
33
  let lastErr;
34
34
  for (let i = 0; i < this.maxRetries; i++) {
35
35
  try {
@@ -65,9 +65,9 @@ export class AnthropicProvider {
65
65
  }
66
66
  throw lastErr;
67
67
  }
68
- async *stream(messages, tools, extensions) {
69
- const system = splitAnthropicSystem(messages);
70
- const msgs = this.buildMessages(messages);
68
+ async *stream(context, tools, extensions) {
69
+ const system = context.systemText || undefined;
70
+ const msgs = this.buildMessages(context);
71
71
  const toolBlocks = {};
72
72
  const nativeBlocks = {};
73
73
  let finalText = "";
@@ -121,8 +121,8 @@ export class AnthropicProvider {
121
121
  }
122
122
  this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
123
123
  }
124
- buildMessages(messages) {
125
- return toAnthropicMessages(messages, message => this.nativeAssistantBlocks.get(this.assistantReplayKey(message)));
124
+ buildMessages(context) {
125
+ return toAnthropicMessages(context.turns, message => this.nativeAssistantBlocks.get(this.assistantReplayKey(message)));
126
126
  }
127
127
  rememberNativeBlocks(message, blocks) {
128
128
  if (!message.toolCalls?.length)
@@ -1,3 +1,4 @@
1
+ import type { Message, RenderedContext } from "../types.js";
1
2
  export declare class CircuitBreaker {
2
3
  private readonly openAfter;
3
4
  private readonly resetAfter;
@@ -13,9 +14,11 @@ export declare function normalizeToolCall(id: string, name: string, args: unknow
13
14
  name: string;
14
15
  arguments: string;
15
16
  } | null;
16
- import type { Message } from "../types.js";
17
17
  export declare function toAnthropicContent(msg: Message): string | Array<Record<string, unknown>>;
18
+ /** Convert RenderedContext.turns to Anthropic messages array.
19
+ * `turns` contains only user / assistant / tool roles — no system filtering needed. */
20
+ export declare function toAnthropicMessages(turns: Message[], nativeReplay?: (message: Message) => Array<Record<string, unknown>> | undefined): Array<Record<string, unknown>>;
18
21
  export declare function toOpenAIContent(msg: Message): string | Array<Record<string, unknown>>;
19
- export declare function splitAnthropicSystem(messages: Message[]): string;
20
- export declare function toAnthropicMessages(messages: Message[], nativeReplay?: (message: Message) => Array<Record<string, unknown>> | undefined): Array<Record<string, unknown>>;
21
- export declare function toOpenAIMessageParams(messages: Message[]): Array<Record<string, unknown>>;
22
+ /** Build the full OpenAI messages array from a RenderedContext.
23
+ * Prepends systemText as the first system message, then converts turns. */
24
+ export declare function toOpenAIMessageParams(context: RenderedContext): Array<Record<string, unknown>>;
@@ -44,6 +44,15 @@ export function normalizeToolCall(id, name, args) {
44
44
  }
45
45
  return { id: String(id ?? ""), name: n, arguments: JSON.stringify(parsed) };
46
46
  }
47
+ function parseToolArguments(args) {
48
+ try {
49
+ return JSON.parse(args || "{}");
50
+ }
51
+ catch {
52
+ return {};
53
+ }
54
+ }
55
+ // ─── Anthropic message conversion ────────────────────────────────────────────
47
56
  export function toAnthropicContent(msg) {
48
57
  if (!msg.contentParts?.length)
49
58
  return msg.content;
@@ -65,39 +74,11 @@ export function toAnthropicContent(msg) {
65
74
  return { type: "text", text: "" };
66
75
  });
67
76
  }
68
- export function toOpenAIContent(msg) {
69
- if (!msg.contentParts?.length)
70
- return msg.content;
71
- return msg.contentParts.map(p => {
72
- if (p.type === "text")
73
- return { type: "text", text: p.text };
74
- if (p.type === "image") {
75
- const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : p.url;
76
- return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
77
- }
78
- if (p.type === "audio") {
79
- return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
80
- }
81
- if (p.type === "tool_result") {
82
- return { type: "text", text: p.output };
83
- }
84
- return { type: "text", text: "" };
85
- });
86
- }
87
- function parseToolArguments(args) {
88
- try {
89
- return JSON.parse(args || "{}");
90
- }
91
- catch {
92
- return {};
93
- }
94
- }
95
- export function splitAnthropicSystem(messages) {
96
- return messages.filter(m => m.role === "system").map(m => m.content).join("\n\n");
97
- }
98
- export function toAnthropicMessages(messages, nativeReplay) {
77
+ /** Convert RenderedContext.turns to Anthropic messages array.
78
+ * `turns` contains only user / assistant / tool roles — no system filtering needed. */
79
+ export function toAnthropicMessages(turns, nativeReplay) {
99
80
  const result = [];
100
- for (const msg of messages.filter(m => m.role !== "system")) {
81
+ for (const msg of turns) {
101
82
  if (msg.role === "tool") {
102
83
  const parts = (msg.contentParts ?? [])
103
84
  .filter((p) => p.type === "tool_result")
@@ -124,16 +105,38 @@ export function toAnthropicMessages(messages, nativeReplay) {
124
105
  result.push({ role: "assistant", content: blocks });
125
106
  continue;
126
107
  }
127
- result.push({
128
- role: msg.role,
129
- content: toAnthropicContent(msg),
130
- });
108
+ result.push({ role: msg.role, content: toAnthropicContent(msg) });
131
109
  }
132
110
  return result;
133
111
  }
134
- export function toOpenAIMessageParams(messages) {
112
+ // ─── OpenAI-compatible message conversion ────────────────────────────────────
113
+ export function toOpenAIContent(msg) {
114
+ if (!msg.contentParts?.length)
115
+ return msg.content;
116
+ return msg.contentParts.map(p => {
117
+ if (p.type === "text")
118
+ return { type: "text", text: p.text };
119
+ if (p.type === "image") {
120
+ const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : p.url;
121
+ return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
122
+ }
123
+ if (p.type === "audio") {
124
+ return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
125
+ }
126
+ if (p.type === "tool_result") {
127
+ return { type: "text", text: p.output };
128
+ }
129
+ return { type: "text", text: "" };
130
+ });
131
+ }
132
+ /** Build the full OpenAI messages array from a RenderedContext.
133
+ * Prepends systemText as the first system message, then converts turns. */
134
+ export function toOpenAIMessageParams(context) {
135
135
  const result = [];
136
- for (const msg of messages) {
136
+ if (context.systemText) {
137
+ result.push({ role: "system", content: context.systemText });
138
+ }
139
+ for (const msg of context.turns) {
137
140
  if (msg.role === "tool") {
138
141
  const parts = (msg.contentParts ?? [])
139
142
  .filter((p) => p.type === "tool_result");
@@ -1,9 +1,9 @@
1
- import type { Message, ToolSchema, StreamEvent } from "../types.js";
1
+ import type { RenderedContext, ToolSchema, StreamEvent } from "../types.js";
2
2
  import { OpenAIChatProvider } from "./openai.js";
3
3
  export declare class DeepSeekProvider extends OpenAIChatProvider {
4
4
  constructor(apiKey: string, model?: "deepseek-v4-flash" | "deepseek-v4-pro", retry?: {
5
5
  maxRetries: number;
6
6
  baseDelay: number;
7
7
  }, baseURL?: string);
8
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
8
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
9
  }
@@ -5,11 +5,11 @@ export class DeepSeekProvider extends OpenAIChatProvider {
5
5
  constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = DEEPSEEK_BASE) {
6
6
  super(apiKey, model, retry, baseURL);
7
7
  }
8
- async *stream(messages, tools, extensions) {
8
+ async *stream(context, tools, extensions) {
9
9
  const exposeReasoning = extensions?.exposeReasoning ?? false;
10
10
  const thinking = extensions?.thinking === false ? "disabled" : "enabled";
11
11
  const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
12
- const msgs = this.chat.buildMessages(messages);
12
+ const msgs = this.chat.buildMessages(context);
13
13
  const toolCallBufs = {};
14
14
  let reasoningContent = "";
15
15
  let finalText = "";
@@ -1,4 +1,4 @@
1
- import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
1
+ import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
2
2
  export declare class GeminiProvider implements LLMProvider {
3
3
  private readonly model;
4
4
  private genAI;
@@ -9,6 +9,6 @@ export declare class GeminiProvider implements LLMProvider {
9
9
  maxRetries: number;
10
10
  baseDelay: number;
11
11
  }, baseURL?: string);
12
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
13
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
12
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
13
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
14
14
  }
@@ -3,11 +3,9 @@ import { withServerRuntimeGuard } from "../runtime/server.js";
3
3
  import { CircuitBreaker, normalizeToolCall } from "./base.js";
4
4
  import { endpointProfiles } from "./profiles.js";
5
5
  const GEMINI_BASE = endpointProfiles["gemini.google"].baseURL;
6
- function buildContents(messages) {
6
+ function buildContents(turns) {
7
7
  const contents = [];
8
- for (const msg of messages) {
9
- if (msg.role === "system")
10
- continue;
8
+ for (const msg of turns) {
11
9
  if (msg.role === "tool") {
12
10
  const parts = (msg.contentParts ?? [])
13
11
  .filter(p => p.type === "tool_result")
@@ -50,9 +48,6 @@ function buildTools(tools) {
50
48
  })),
51
49
  }];
52
50
  }
53
- function systemInstruction(messages) {
54
- return messages.find(m => m.role === "system")?.content;
55
- }
56
51
  export class GeminiProvider {
57
52
  model;
58
53
  genAI;
@@ -66,11 +61,11 @@ export class GeminiProvider {
66
61
  this.maxRetries = retry.maxRetries;
67
62
  this.baseDelay = retry.baseDelay;
68
63
  }
69
- async complete(messages, tools) {
64
+ async complete(context, tools) {
70
65
  if (this.circuit.isOpen())
71
66
  throw new Error("Circuit breaker open");
72
- const system = systemInstruction(messages);
73
- const contents = buildContents(messages);
67
+ const system = context.systemText || undefined;
68
+ const contents = buildContents(context.turns);
74
69
  const geminiTools = buildTools(tools);
75
70
  let lastErr;
76
71
  for (let i = 0; i < this.maxRetries; i++) {
@@ -111,9 +106,9 @@ export class GeminiProvider {
111
106
  }
112
107
  throw lastErr;
113
108
  }
114
- async *stream(messages, tools, extensions) {
115
- const system = systemInstruction(messages);
116
- const contents = buildContents(messages);
109
+ async *stream(context, tools, extensions) {
110
+ const system = context.systemText || undefined;
111
+ const contents = buildContents(context.turns);
117
112
  const geminiTools = buildTools(tools);
118
113
  const m = this.genAI.getGenerativeModel({
119
114
  model: this.model,
@@ -1,9 +1,9 @@
1
- import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
1
+ import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
2
2
  export declare class OllamaProvider implements LLMProvider {
3
3
  private readonly model;
4
4
  private readonly baseUrl;
5
5
  constructor(model?: string, baseUrl?: string);
6
6
  private toOllamaMessages;
7
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
8
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
7
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
8
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
9
  }
@@ -6,8 +6,11 @@ export class OllamaProvider {
6
6
  this.model = model;
7
7
  this.baseUrl = baseUrl;
8
8
  }
9
- toOllamaMessages(messages) {
10
- return messages.map(m => {
9
+ toOllamaMessages(context) {
10
+ const result = [];
11
+ if (context.systemText)
12
+ result.push({ role: "system", content: context.systemText });
13
+ for (const m of context.turns) {
11
14
  const images = [];
12
15
  if (m.contentParts?.length) {
13
16
  for (const p of m.contentParts) {
@@ -15,25 +18,26 @@ export class OllamaProvider {
15
18
  images.push(p.data);
16
19
  }
17
20
  }
18
- return { role: m.role, content: m.content, ...(images.length ? { images } : {}) };
19
- });
21
+ result.push({ role: m.role, content: m.content, ...(images.length ? { images } : {}) });
22
+ }
23
+ return result;
20
24
  }
21
- async complete(messages, tools) {
25
+ async complete(context, tools) {
22
26
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
23
27
  method: "POST",
24
28
  headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: false }),
29
+ body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(context), stream: false }),
26
30
  });
27
31
  if (!resp.ok)
28
32
  throw new Error(`Ollama error: ${resp.status}`);
29
33
  const data = await resp.json();
30
34
  return { role: "assistant", content: data.message.content };
31
35
  }
32
- async *stream(messages, tools, extensions) {
36
+ async *stream(context, tools, extensions) {
33
37
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
34
38
  method: "POST",
35
39
  headers: { "Content-Type": "application/json" },
36
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: true }),
40
+ body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(context), stream: true }),
37
41
  });
38
42
  if (!resp.ok)
39
43
  throw new Error(`Ollama error: ${resp.status}`);
@@ -1,5 +1,5 @@
1
1
  import type OpenAI from "openai";
2
- import type { Message, ToolSchema } from "../types.js";
2
+ import type { Message, RenderedContext, ToolSchema } from "../types.js";
3
3
  export declare class OpenAIChatAdapter {
4
4
  private replayFields;
5
5
  buildTools(tools: ToolSchema[]): {
@@ -10,7 +10,7 @@ export declare class OpenAIChatAdapter {
10
10
  parameters: any;
11
11
  };
12
12
  }[];
13
- buildMessages(messages: Message[]): OpenAI.ChatCompletionMessageParam[];
13
+ buildMessages(context: RenderedContext): OpenAI.ChatCompletionMessageParam[];
14
14
  normalizeToolCalls(toolCalls?: Array<{
15
15
  id: string;
16
16
  function: {
@@ -7,10 +7,12 @@ export class OpenAIChatAdapter {
7
7
  function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
8
8
  }));
9
9
  }
10
- buildMessages(messages) {
11
- const serialized = toOpenAIMessageParams(messages);
12
- let cursor = 0;
13
- for (const source of messages) {
10
+ buildMessages(context) {
11
+ // toOpenAIMessageParams prepends systemText as messages[0], then turns.
12
+ const serialized = toOpenAIMessageParams(context);
13
+ // Cursor starts at 1 to skip the system message injected by toOpenAIMessageParams.
14
+ let cursor = context.systemText ? 1 : 0;
15
+ for (const source of context.turns) {
14
16
  if (source.role === "tool") {
15
17
  cursor += (source.contentParts ?? []).filter(p => p.type === "tool_result").length;
16
18
  continue;
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import type { Message, ProviderRunState, StreamEvent, ToolSchema, LLMProvider } from "../types.js";
2
+ import type { Message, ProviderRunState, RenderedContext, StreamEvent, ToolSchema, LLMProvider } from "../types.js";
3
3
  import { CircuitBreaker } from "./base.js";
4
4
  export interface OpenAIResponsesRunState extends ProviderRunState {
5
5
  previousResponseId?: string;
@@ -12,8 +12,8 @@ export declare class OpenAIResponsesAdapter {
12
12
  description: string;
13
13
  parameters: any;
14
14
  }[];
15
- buildInstructions(messages: Message[]): string | undefined;
16
- buildInput(messages: Message[], state?: OpenAIResponsesRunState): Array<Record<string, unknown>>;
15
+ buildInstructions(context: RenderedContext): string | undefined;
16
+ buildInput(context: RenderedContext, state?: OpenAIResponsesRunState): Array<Record<string, unknown>>;
17
17
  decodeOutput(output: Array<Record<string, unknown>>): {
18
18
  content: string;
19
19
  toolCalls: Array<{
@@ -36,7 +36,7 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
36
36
  baseDelay: number;
37
37
  }, baseURL?: string);
38
38
  createRunState(): OpenAIResponsesRunState;
39
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
40
- stream(messages: Message[], tools: ToolSchema[], _extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
39
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
40
+ stream(context: RenderedContext, tools: ToolSchema[], _extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
41
41
  private asRunState;
42
42
  }
@@ -11,22 +11,16 @@ export class OpenAIResponsesAdapter {
11
11
  parameters: JSON.parse(t.parameters),
12
12
  }));
13
13
  }
14
- buildInstructions(messages) {
15
- const instructions = messages
16
- .filter(message => message.role === "system")
17
- .map(message => message.content)
18
- .filter(Boolean);
19
- return instructions.length ? instructions.join("\n\n") : undefined;
14
+ buildInstructions(context) {
15
+ return context.systemText || undefined;
20
16
  }
21
- buildInput(messages, state) {
17
+ buildInput(context, state) {
22
18
  const input = [];
19
+ const turns = context.turns;
23
20
  const uncoveredMessages = state?.previousResponseId
24
- ? messages.slice(state.coveredMessageCount)
25
- : messages;
21
+ ? turns.slice(state.coveredMessageCount)
22
+ : turns;
26
23
  for (const message of uncoveredMessages) {
27
- if (message.role === "system") {
28
- continue;
29
- }
30
24
  if (message.role === "assistant" && message.toolCalls?.length) {
31
25
  if (message.content || message.contentParts?.length) {
32
26
  input.push({
@@ -122,16 +116,16 @@ export class OpenAIResponsesProvider {
122
116
  createRunState() {
123
117
  return { coveredMessageCount: 0 };
124
118
  }
125
- async complete(messages, tools) {
119
+ async complete(context, tools) {
126
120
  if (this.circuit.isOpen())
127
121
  throw new Error("Circuit breaker open");
128
122
  let lastErr;
129
123
  for (let i = 0; i < this.maxRetries; i++) {
130
124
  try {
131
- const instructions = this.responses.buildInstructions(messages);
125
+ const instructions = this.responses.buildInstructions(context);
132
126
  const resp = await this.client.responses.create({
133
127
  model: this.model,
134
- input: this.responses.buildInput(messages),
128
+ input: this.responses.buildInput(context),
135
129
  ...(instructions ? { instructions } : {}),
136
130
  ...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
137
131
  });
@@ -153,13 +147,13 @@ export class OpenAIResponsesProvider {
153
147
  }
154
148
  throw lastErr;
155
149
  }
156
- async *stream(messages, tools, _extensions, state) {
150
+ async *stream(context, tools, _extensions, state) {
157
151
  const runState = this.asRunState(state);
158
152
  const functionCalls = new Map();
159
- const instructions = this.responses.buildInstructions(messages);
153
+ const instructions = this.responses.buildInstructions(context);
160
154
  const stream = await this.client.responses.create({
161
155
  model: this.model,
162
- input: this.responses.buildInput(messages, runState),
156
+ input: this.responses.buildInput(context, runState),
163
157
  ...(instructions ? { instructions } : {}),
164
158
  ...(runState.previousResponseId ? { previous_response_id: runState.previousResponseId } : {}),
165
159
  ...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
@@ -203,7 +197,7 @@ export class OpenAIResponsesProvider {
203
197
  }
204
198
  else if (evt.type === "response.completed") {
205
199
  runState.previousResponseId = evt.response.id;
206
- runState.coveredMessageCount = messages.length + 1;
200
+ runState.coveredMessageCount = context.turns.length + 1;
207
201
  if (evt.response.usage?.total_tokens) {
208
202
  yield { type: "usage", totalTokens: evt.response.usage.total_tokens };
209
203
  }
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
2
+ import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
3
3
  import { CircuitBreaker } from "./base.js";
4
4
  import { OpenAIChatAdapter } from "./openai-chat.js";
5
5
  export declare class OpenAIChatProvider implements LLMProvider {
@@ -13,7 +13,7 @@ export declare class OpenAIChatProvider implements LLMProvider {
13
13
  maxRetries: number;
14
14
  baseDelay: number;
15
15
  }, baseURL?: string);
16
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
17
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
16
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
17
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
18
  }
19
19
  export { OpenAIChatProvider as OpenAIProvider };
@@ -16,10 +16,10 @@ export class OpenAIChatProvider {
16
16
  this.maxRetries = retry.maxRetries;
17
17
  this.baseDelay = retry.baseDelay;
18
18
  }
19
- async complete(messages, tools) {
19
+ async complete(context, tools) {
20
20
  if (this.circuit.isOpen())
21
21
  throw new Error("Circuit breaker open");
22
- const msgs = this.chat.buildMessages(messages);
22
+ const msgs = this.chat.buildMessages(context);
23
23
  let lastErr;
24
24
  for (let i = 0; i < this.maxRetries; i++) {
25
25
  try {
@@ -42,8 +42,8 @@ export class OpenAIChatProvider {
42
42
  }
43
43
  throw lastErr;
44
44
  }
45
- async *stream(messages, tools, extensions) {
46
- const msgs = this.chat.buildMessages(messages);
45
+ async *stream(context, tools, extensions) {
46
+ const msgs = this.chat.buildMessages(context);
47
47
  const toolCallBufs = {};
48
48
  const stream = await this.client.chat.completions.create({
49
49
  model: this.model,
@@ -1,5 +1,5 @@
1
1
  import OpenAI from "openai";
2
- import type { LLMProvider, Message, StreamEvent, ToolSchema } from "../types.js";
2
+ import type { LLMProvider, Message, RenderedContext, StreamEvent, ToolSchema } from "../types.js";
3
3
  import { CircuitBreaker } from "./base.js";
4
4
  import { OpenAIChatAdapter } from "./openai-chat.js";
5
5
  export declare class QwenProvider implements LLMProvider {
@@ -13,6 +13,6 @@ export declare class QwenProvider implements LLMProvider {
13
13
  maxRetries: number;
14
14
  baseDelay: number;
15
15
  }, baseURL?: string);
16
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
17
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
16
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
17
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
18
  }
@@ -18,10 +18,10 @@ export class QwenProvider {
18
18
  this.maxRetries = retry.maxRetries;
19
19
  this.baseDelay = retry.baseDelay;
20
20
  }
21
- async complete(messages, tools) {
21
+ async complete(context, tools) {
22
22
  if (this.circuit.isOpen())
23
23
  throw new Error("Circuit breaker open");
24
- const msgs = this.chat.buildMessages(messages);
24
+ const msgs = this.chat.buildMessages(context);
25
25
  let lastErr;
26
26
  for (let i = 0; i < this.maxRetries; i++) {
27
27
  try {
@@ -44,10 +44,10 @@ export class QwenProvider {
44
44
  }
45
45
  throw lastErr;
46
46
  }
47
- async *stream(messages, tools, extensions) {
47
+ async *stream(context, tools, extensions) {
48
48
  const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
49
49
  const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
50
- const msgs = this.chat.buildMessages(messages);
50
+ const msgs = this.chat.buildMessages(context);
51
51
  const toolCallBufs = {};
52
52
  const stream = await this.client.chat.completions.create({
53
53
  model: this.model,
package/dist/types.d.ts CHANGED
@@ -117,8 +117,18 @@ export interface RetryConfig {
117
117
  * Responses `previous_response_id` without leaking those semantics into the kernel.
118
118
  */
119
119
  export type ProviderRunState = Record<string, unknown>;
120
+ /** Structured render output produced by the kernel for each LLM call. */
121
+ export interface RenderedContext {
122
+ /** Combined system text: system partition + dashboard (when non-empty).
123
+ * Anthropic → `system` param · OpenAI → messages[0] system role ·
124
+ * Gemini → `systemInstruction`. */
125
+ systemText: string;
126
+ /** Strictly alternating user / assistant / tool turns.
127
+ * Working-partition signals are already folded into the first user turn. */
128
+ turns: Message[];
129
+ }
120
130
  export interface LLMProvider {
121
131
  createRunState?(): ProviderRunState;
122
- complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
123
- stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
132
+ complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
133
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
124
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/sdk",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "DeepStrike Node.js SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@anthropic-ai/sdk": "^0.39.0",
19
- "@deepstrike/core": "0.1.11",
19
+ "@deepstrike/core": "0.1.12",
20
20
  "@google/generative-ai": "^0.24.1",
21
21
  "openai": "^4.77.0"
22
22
  },