@toren-run/providers 0.1.1 → 0.1.3

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/dist/openai.d.ts CHANGED
@@ -2,6 +2,10 @@ import type OpenAI from "openai";
2
2
  import type { ModelProvider, ModelRequest, ModelResponse } from "@toren-run/core";
3
3
  export declare function toOpenAIParams(req: ModelRequest): OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming;
4
4
  export declare function fromOpenAIResponse(completion: OpenAI.Chat.Completions.ChatCompletion): ModelResponse;
5
+ /** Real reasoning effort ("low"/"medium"/"high") needs /v1/responses; "none" or absent works on chat/completions. */
6
+ export declare function usesResponsesApi(req: Pick<ModelRequest, "reasoningEffort">): boolean;
7
+ export declare function toResponsesParams(req: ModelRequest): OpenAI.Responses.ResponseCreateParamsNonStreaming;
8
+ export declare function fromResponsesResponse(res: OpenAI.Responses.Response): ModelResponse;
5
9
  /**
6
10
  * OpenAI adapter over toren's normalized model interface.
7
11
  * Credentials resolve from the environment (OPENAI_API_KEY);
package/dist/openai.js CHANGED
@@ -64,6 +64,8 @@ export function toOpenAIParams(req) {
64
64
  return {
65
65
  model: req.model.replace(/^openai\//, ""),
66
66
  max_completion_tokens: req.maxTokens,
67
+ // gpt-5.6+ reject tools via chat/completions unless reasoning_effort is set.
68
+ ...(req.reasoningEffort ? { reasoning_effort: req.reasoningEffort } : {}),
67
69
  messages: [
68
70
  ...(req.system ? [{ role: "system", content: req.system }] : []),
69
71
  ...toSdkMessages(req.messages),
@@ -113,6 +115,82 @@ export function fromOpenAIResponse(completion) {
113
115
  },
114
116
  };
115
117
  }
118
+ // ---- /v1/responses: the API that allows reasoning AND tools together.
119
+ // gpt-5.6+ reject function tools on chat/completions unless reasoning is off,
120
+ // so a request with real reasoning effort routes here; everything else stays
121
+ // on the battle-tested chat/completions path. The digest covers toren's
122
+ // normalized request, not the wire shape, so routing changes nothing in-flight.
123
+ /** Real reasoning effort ("low"/"medium"/"high") needs /v1/responses; "none" or absent works on chat/completions. */
124
+ export function usesResponsesApi(req) {
125
+ return Boolean(req.reasoningEffort && req.reasoningEffort !== "none");
126
+ }
127
+ export function toResponsesParams(req) {
128
+ const input = [];
129
+ for (const m of req.messages) {
130
+ if (m.role === "assistant") {
131
+ const text = m.content.filter((b) => b.type === "text").map((b) => b.text).join("");
132
+ if (text)
133
+ input.push({ role: "assistant", content: [{ type: "output_text", text, annotations: [] }] });
134
+ for (const b of m.content) {
135
+ if (b.type === "toolUse")
136
+ input.push({ type: "function_call", call_id: b.id, name: b.name, arguments: JSON.stringify(b.input ?? {}) });
137
+ }
138
+ continue;
139
+ }
140
+ let text = "";
141
+ for (const b of m.content) {
142
+ if (b.type === "toolResult")
143
+ input.push({ type: "function_call_output", call_id: b.toolUseId, output: b.isError ? `ERROR: ${b.content}` : b.content });
144
+ else if (b.type === "text")
145
+ text += b.text;
146
+ }
147
+ if (text)
148
+ input.push({ role: "user", content: [{ type: "input_text", text }] });
149
+ }
150
+ return {
151
+ model: req.model.replace(/^openai\//, ""),
152
+ ...(req.system ? { instructions: req.system } : {}),
153
+ max_output_tokens: req.maxTokens,
154
+ reasoning: { effort: req.reasoningEffort },
155
+ input,
156
+ ...(req.tools.length
157
+ ? { tools: req.tools.map((t) => ({ type: "function", name: t.name, description: t.description, parameters: t.inputSchema, strict: false })) }
158
+ : {}),
159
+ };
160
+ }
161
+ export function fromResponsesResponse(res) {
162
+ const content = [];
163
+ for (const item of res.output ?? []) {
164
+ if (item.type === "message") {
165
+ const text = item.content.filter((c) => c.type === "output_text").map((c) => c.text).join("");
166
+ if (text)
167
+ content.push({ type: "text", text });
168
+ }
169
+ else if (item.type === "function_call") {
170
+ let input = {};
171
+ try {
172
+ input = JSON.parse(item.arguments || "{}");
173
+ }
174
+ catch {
175
+ input = { _raw: item.arguments };
176
+ }
177
+ content.push({ type: "toolUse", id: item.call_id, name: item.name, input });
178
+ }
179
+ // reasoning items are the model's private scratchpad: not transcript content.
180
+ }
181
+ const stopReason = content.some((b) => b.type === "toolUse")
182
+ ? "toolUse"
183
+ : res.status === "incomplete" && res.incomplete_details?.reason === "max_output_tokens"
184
+ ? "maxTokens"
185
+ : res.status === "incomplete" && res.incomplete_details?.reason === "content_filter"
186
+ ? "refusal"
187
+ : "endTurn";
188
+ return {
189
+ content,
190
+ stopReason,
191
+ usage: { inputTokens: res.usage?.input_tokens ?? 0, outputTokens: res.usage?.output_tokens ?? 0 },
192
+ };
193
+ }
116
194
  /**
117
195
  * OpenAI adapter over toren's normalized model interface.
118
196
  * Credentials resolve from the environment (OPENAI_API_KEY);
@@ -124,6 +202,9 @@ export class OpenAIProvider {
124
202
  this.client = client ?? new (loadSdk())();
125
203
  }
126
204
  async complete(req) {
205
+ if (usesResponsesApi(req)) {
206
+ return fromResponsesResponse(await this.client.responses.create(toResponsesParams(req)));
207
+ }
127
208
  const response = await this.client.chat.completions.create(toOpenAIParams(req));
128
209
  return fromOpenAIResponse(response);
129
210
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toren-run/providers",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "@anthropic-ai/sdk": "^0.110.0",
13
13
  "openai": "^4.68.0",
14
- "@toren-run/core": "^0.1.1"
14
+ "@toren-run/core": "^0.1.3"
15
15
  },
16
16
  "description": "Model provider adapters for Toren (Anthropic, OpenAI) — each SDK loads lazily on first use.",
17
17
  "license": "Apache-2.0",