@deepstrike/sdk 0.1.11 → 0.1.13

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.
@@ -26,6 +26,12 @@ export class CircuitBreaker {
26
26
  this.openedAt = Date.now();
27
27
  }
28
28
  }
29
+ export function omitExtensionKeys(extensions, keys) {
30
+ if (!extensions)
31
+ return {};
32
+ const blocked = new Set(keys);
33
+ return Object.fromEntries(Object.entries(extensions).filter(([key]) => !blocked.has(key)));
34
+ }
29
35
  export function normalizeToolCall(id, name, args) {
30
36
  const n = String(name ?? "").trim();
31
37
  if (!n)
@@ -44,6 +50,15 @@ export function normalizeToolCall(id, name, args) {
44
50
  }
45
51
  return { id: String(id ?? ""), name: n, arguments: JSON.stringify(parsed) };
46
52
  }
53
+ function parseToolArguments(args) {
54
+ try {
55
+ return JSON.parse(args || "{}");
56
+ }
57
+ catch {
58
+ return {};
59
+ }
60
+ }
61
+ // ─── Anthropic message conversion ────────────────────────────────────────────
47
62
  export function toAnthropicContent(msg) {
48
63
  if (!msg.contentParts?.length)
49
64
  return msg.content;
@@ -65,39 +80,11 @@ export function toAnthropicContent(msg) {
65
80
  return { type: "text", text: "" };
66
81
  });
67
82
  }
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) {
83
+ /** Convert RenderedContext.turns to Anthropic messages array.
84
+ * `turns` contains only user / assistant / tool roles — no system filtering needed. */
85
+ export function toAnthropicMessages(turns, nativeReplay) {
99
86
  const result = [];
100
- for (const msg of messages.filter(m => m.role !== "system")) {
87
+ for (const msg of turns) {
101
88
  if (msg.role === "tool") {
102
89
  const parts = (msg.contentParts ?? [])
103
90
  .filter((p) => p.type === "tool_result")
@@ -124,16 +111,38 @@ export function toAnthropicMessages(messages, nativeReplay) {
124
111
  result.push({ role: "assistant", content: blocks });
125
112
  continue;
126
113
  }
127
- result.push({
128
- role: msg.role,
129
- content: toAnthropicContent(msg),
130
- });
114
+ result.push({ role: msg.role, content: toAnthropicContent(msg) });
131
115
  }
132
116
  return result;
133
117
  }
134
- export function toOpenAIMessageParams(messages) {
118
+ // ─── OpenAI-compatible message conversion ────────────────────────────────────
119
+ export function toOpenAIContent(msg) {
120
+ if (!msg.contentParts?.length)
121
+ return msg.content;
122
+ return msg.contentParts.map(p => {
123
+ if (p.type === "text")
124
+ return { type: "text", text: p.text };
125
+ if (p.type === "image") {
126
+ const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : p.url;
127
+ return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
128
+ }
129
+ if (p.type === "audio") {
130
+ return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
131
+ }
132
+ if (p.type === "tool_result") {
133
+ return { type: "text", text: p.output };
134
+ }
135
+ return { type: "text", text: "" };
136
+ });
137
+ }
138
+ /** Build the full OpenAI messages array from a RenderedContext.
139
+ * Prepends systemText as the first system message, then converts turns. */
140
+ export function toOpenAIMessageParams(context) {
135
141
  const result = [];
136
- for (const msg of messages) {
142
+ if (context.systemText) {
143
+ result.push({ role: "system", content: context.systemText });
144
+ }
145
+ for (const msg of context.turns) {
137
146
  if (msg.role === "tool") {
138
147
  const parts = (msg.contentParts ?? [])
139
148
  .filter((p) => p.type === "tool_result");
@@ -1,9 +1,10 @@
1
- import type { Message, ToolSchema, StreamEvent } from "../types.js";
1
+ import type { Message, 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
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
9
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
10
  }
@@ -1,19 +1,34 @@
1
1
  import { OpenAIChatProvider } from "./openai.js";
2
2
  import { endpointProfiles } from "./profiles.js";
3
+ import { omitExtensionKeys } from "./base.js";
3
4
  const DEEPSEEK_BASE = endpointProfiles["deepseek.openai"].baseURL;
4
5
  export class DeepSeekProvider extends OpenAIChatProvider {
5
6
  constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = DEEPSEEK_BASE) {
6
7
  super(apiKey, model, retry, baseURL);
7
8
  }
8
- async *stream(messages, tools, extensions) {
9
+ async complete(context, tools, extensions) {
10
+ const thinking = extensions?.thinking === false ? "disabled" : "enabled";
11
+ const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
12
+ return super.complete(context, tools, {
13
+ ...omitExtensionKeys(extensions, ["thinking", "reasoningEffort", "exposeReasoning", "extra_body", "reasoning_effort"]),
14
+ reasoning_effort: reasoningEffort,
15
+ extra_body: { thinking: { type: thinking } },
16
+ });
17
+ }
18
+ async *stream(context, tools, extensions) {
9
19
  const exposeReasoning = extensions?.exposeReasoning ?? false;
10
20
  const thinking = extensions?.thinking === false ? "disabled" : "enabled";
11
21
  const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
12
- const msgs = this.chat.buildMessages(messages);
22
+ const msgs = this.chat.buildMessages(context);
13
23
  const toolCallBufs = {};
24
+ const emittedToolCallIndexes = new Set();
14
25
  let reasoningContent = "";
15
26
  let finalText = "";
16
27
  const stream = await this.client.chat.completions.create({
28
+ ...omitExtensionKeys(extensions, [
29
+ "model", "messages", "tools", "stream", "extra_body", "reasoning_effort",
30
+ "exposeReasoning", "thinking", "reasoningEffort",
31
+ ]),
17
32
  model: this.model,
18
33
  messages: msgs,
19
34
  ...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
@@ -48,7 +63,10 @@ export class DeepSeekProvider extends OpenAIChatProvider {
48
63
  id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
49
64
  }));
50
65
  this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
51
- for (const tb of Object.values(toolCallBufs)) {
66
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
67
+ const idx = Number(index);
68
+ if (emittedToolCallIndexes.has(idx))
69
+ continue;
52
70
  let args = {};
53
71
  try {
54
72
  args = JSON.parse(tb.argsBuf || "{}");
@@ -56,9 +74,30 @@ export class DeepSeekProvider extends OpenAIChatProvider {
56
74
  catch {
57
75
  args = {};
58
76
  }
77
+ emittedToolCallIndexes.add(idx);
59
78
  yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
60
79
  }
61
80
  }
62
81
  }
82
+ const toolCalls = Object.values(toolCallBufs).map(tb => ({
83
+ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
84
+ }));
85
+ if (toolCalls.length) {
86
+ this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
87
+ }
88
+ for (const [index, tb] of Object.entries(toolCallBufs)) {
89
+ const idx = Number(index);
90
+ if (emittedToolCallIndexes.has(idx))
91
+ continue;
92
+ let args = {};
93
+ try {
94
+ args = JSON.parse(tb.argsBuf || "{}");
95
+ }
96
+ catch {
97
+ args = {};
98
+ }
99
+ emittedToolCallIndexes.add(idx);
100
+ yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
101
+ }
63
102
  }
64
103
  }
@@ -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,7 @@ 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[], extensions?: Record<string, unknown>): Promise<Message>;
13
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
14
+ private modelExtensions;
14
15
  }
@@ -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,16 +61,17 @@ 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, extensions) {
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++) {
77
72
  try {
78
73
  const m = this.genAI.getGenerativeModel({
74
+ ...this.modelExtensions(extensions),
79
75
  model: this.model,
80
76
  ...(system ? { systemInstruction: system } : {}),
81
77
  ...(geminiTools.length ? { tools: geminiTools } : {}),
@@ -111,32 +107,39 @@ export class GeminiProvider {
111
107
  }
112
108
  throw lastErr;
113
109
  }
114
- async *stream(messages, tools, extensions) {
115
- const system = systemInstruction(messages);
116
- const contents = buildContents(messages);
110
+ async *stream(context, tools, extensions) {
111
+ const system = context.systemText || undefined;
112
+ const contents = buildContents(context.turns);
117
113
  const geminiTools = buildTools(tools);
118
114
  const m = this.genAI.getGenerativeModel({
115
+ ...this.modelExtensions(extensions),
119
116
  model: this.model,
120
117
  ...(system ? { systemInstruction: system } : {}),
121
118
  ...(geminiTools.length ? { tools: geminiTools } : {}),
122
119
  });
123
120
  const result = await m.generateContentStream({ contents });
124
- const toolCallBufs = {};
121
+ const toolCalls = [];
125
122
  for await (const chunk of result.stream) {
126
123
  for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
127
124
  if (part.text)
128
125
  yield { type: "text_delta", delta: part.text };
129
126
  else if (part.functionCall) {
130
127
  const { name, args } = part.functionCall;
131
- toolCallBufs[name] = { name, args: args };
128
+ toolCalls.push({ id: `call_${toolCalls.length + 1}`, name, args: args });
132
129
  }
133
130
  }
134
131
  }
135
- for (const [id, tc] of Object.entries(toolCallBufs)) {
136
- yield { type: "tool_call", id, name: tc.name, arguments: tc.args };
132
+ for (const tc of toolCalls) {
133
+ yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.args };
137
134
  }
138
135
  const usage = (await result.response).usageMetadata;
139
136
  if (usage?.totalTokenCount)
140
137
  yield { type: "usage", totalTokens: usage.totalTokenCount };
141
138
  }
139
+ modelExtensions(extensions) {
140
+ if (!extensions)
141
+ return {};
142
+ const { model: _model, systemInstruction: _systemInstruction, tools: _tools, ...rest } = extensions;
143
+ return rest;
144
+ }
142
145
  }
@@ -1,9 +1,11 @@
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
+ private buildTools;
8
+ private requestExtensions;
9
+ complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
10
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
9
11
  }
@@ -1,4 +1,4 @@
1
- import { normalizeToolCall } from "./base.js";
1
+ import { normalizeToolCall, omitExtensionKeys } from "./base.js";
2
2
  export class OllamaProvider {
3
3
  model;
4
4
  baseUrl;
@@ -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,31 +18,54 @@ 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;
24
+ }
25
+ buildTools(tools) {
26
+ return tools.map(t => ({
27
+ type: "function",
28
+ function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
29
+ }));
20
30
  }
21
- async complete(messages, tools) {
31
+ requestExtensions(extensions) {
32
+ return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream"]);
33
+ }
34
+ async complete(context, tools, extensions) {
22
35
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
23
36
  method: "POST",
24
37
  headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: false }),
38
+ body: JSON.stringify({
39
+ ...this.requestExtensions(extensions),
40
+ model: this.model,
41
+ messages: this.toOllamaMessages(context),
42
+ ...(tools.length ? { tools: this.buildTools(tools) } : {}),
43
+ stream: false,
44
+ }),
26
45
  });
27
46
  if (!resp.ok)
28
47
  throw new Error(`Ollama error: ${resp.status}`);
29
48
  const data = await resp.json();
30
49
  return { role: "assistant", content: data.message.content };
31
50
  }
32
- async *stream(messages, tools, extensions) {
51
+ async *stream(context, tools, extensions) {
33
52
  const resp = await fetch(`${this.baseUrl}/api/chat`, {
34
53
  method: "POST",
35
54
  headers: { "Content-Type": "application/json" },
36
- body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: true }),
55
+ body: JSON.stringify({
56
+ ...this.requestExtensions(extensions),
57
+ model: this.model,
58
+ messages: this.toOllamaMessages(context),
59
+ ...(tools.length ? { tools: this.buildTools(tools) } : {}),
60
+ stream: true,
61
+ }),
37
62
  });
38
63
  if (!resp.ok)
39
64
  throw new Error(`Ollama error: ${resp.status}`);
40
65
  const reader = resp.body.getReader();
41
66
  const decoder = new TextDecoder();
42
67
  let buf = "";
68
+ const pendingToolCalls = new Map();
43
69
  while (true) {
44
70
  const { done, value } = await reader.read();
45
71
  if (done)
@@ -55,13 +81,25 @@ export class OllamaProvider {
55
81
  if (chunk.message?.content)
56
82
  yield { type: "text_delta", delta: chunk.message.content };
57
83
  for (const tc of chunk.message?.tool_calls ?? []) {
58
- const norm = normalizeToolCall(crypto.randomUUID(), tc.function.name, tc.function.arguments);
59
- if (norm)
60
- yield { type: "tool_call", id: norm.id, name: norm.name, arguments: JSON.parse(norm.arguments) };
84
+ const norm = normalizeToolCall("", tc.function.name, tc.function.arguments);
85
+ if (!norm)
86
+ continue;
87
+ const args = JSON.parse(norm.arguments);
88
+ const key = `${norm.name}:${norm.arguments}`;
89
+ if (!pendingToolCalls.has(key)) {
90
+ pendingToolCalls.set(key, {
91
+ id: `call_${pendingToolCalls.size + 1}`,
92
+ name: norm.name,
93
+ arguments: args,
94
+ });
95
+ }
61
96
  }
62
97
  }
63
98
  catch { /* skip malformed lines */ }
64
99
  }
65
100
  }
101
+ for (const tc of pendingToolCalls.values()) {
102
+ yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments };
103
+ }
66
104
  }
67
105
  }
@@ -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,8 @@ 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[], extensions?: Record<string, unknown>): Promise<Message>;
40
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
41
+ private requestExtensions;
41
42
  private asRunState;
42
43
  }
@@ -1,6 +1,6 @@
1
1
  import OpenAI from "openai";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker } from "./base.js";
3
+ import { CircuitBreaker, omitExtensionKeys } from "./base.js";
4
4
  import { normalizeToolCall } from "./base.js";
5
5
  export class OpenAIResponsesAdapter {
6
6
  buildTools(tools) {
@@ -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,17 @@ export class OpenAIResponsesProvider {
122
116
  createRunState() {
123
117
  return { coveredMessageCount: 0 };
124
118
  }
125
- async complete(messages, tools) {
119
+ async complete(context, tools, extensions) {
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({
127
+ ...this.requestExtensions(extensions),
133
128
  model: this.model,
134
- input: this.responses.buildInput(messages),
129
+ input: this.responses.buildInput(context),
135
130
  ...(instructions ? { instructions } : {}),
136
131
  ...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
137
132
  });
@@ -153,13 +148,14 @@ export class OpenAIResponsesProvider {
153
148
  }
154
149
  throw lastErr;
155
150
  }
156
- async *stream(messages, tools, _extensions, state) {
151
+ async *stream(context, tools, extensions, state) {
157
152
  const runState = this.asRunState(state);
158
153
  const functionCalls = new Map();
159
- const instructions = this.responses.buildInstructions(messages);
154
+ const instructions = this.responses.buildInstructions(context);
160
155
  const stream = await this.client.responses.create({
156
+ ...this.requestExtensions(extensions),
161
157
  model: this.model,
162
- input: this.responses.buildInput(messages, runState),
158
+ input: this.responses.buildInput(context, runState),
163
159
  ...(instructions ? { instructions } : {}),
164
160
  ...(runState.previousResponseId ? { previous_response_id: runState.previousResponseId } : {}),
165
161
  ...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
@@ -203,13 +199,18 @@ export class OpenAIResponsesProvider {
203
199
  }
204
200
  else if (evt.type === "response.completed") {
205
201
  runState.previousResponseId = evt.response.id;
206
- runState.coveredMessageCount = messages.length + 1;
202
+ runState.coveredMessageCount = context.turns.length + 1;
207
203
  if (evt.response.usage?.total_tokens) {
208
204
  yield { type: "usage", totalTokens: evt.response.usage.total_tokens };
209
205
  }
210
206
  }
211
207
  }
212
208
  }
209
+ requestExtensions(extensions) {
210
+ return omitExtensionKeys(extensions, [
211
+ "model", "input", "instructions", "tools", "stream", "previous_response_id",
212
+ ]);
213
+ }
213
214
  asRunState(state) {
214
215
  if (!state)
215
216
  return this.createRunState();
@@ -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,8 @@ 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[], extensions?: Record<string, unknown>): Promise<Message>;
17
+ stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
+ protected requestExtensions(extensions?: Record<string, unknown>): Record<string, unknown>;
18
19
  }
19
20
  export { OpenAIChatProvider as OpenAIProvider };