@rcrsr/rill-ext-openai 0.8.6 → 0.9.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.
- package/README.md +8 -127
- package/dist/index.d.ts +2 -6
- package/dist/index.js +406 -147
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rcrsr/rill-ext-openai
|
|
2
2
|
|
|
3
|
-
[rill](https://rill.run) extension for [OpenAI](https://platform.openai.com/docs) API integration. Provides `message`, `messages`, `embed`, `embed_batch`, and `
|
|
3
|
+
[rill](https://rill.run) extension for [OpenAI](https://platform.openai.com/docs) API integration. Provides `message`, `messages`, `embed`, `embed_batch`, `tool_loop`, and `generate` host functions. Compatible with any OpenAI-compatible server (LM Studio, Ollama, vLLM).
|
|
4
4
|
|
|
5
5
|
> **Experimental.** Breaking changes will occur before stabilization.
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ import { createOpenAIExtension } from '@rcrsr/rill-ext-openai';
|
|
|
20
20
|
|
|
21
21
|
const ext = createOpenAIExtension({
|
|
22
22
|
api_key: process.env.OPENAI_API_KEY!,
|
|
23
|
-
model: 'gpt-
|
|
23
|
+
model: 'gpt-4o',
|
|
24
24
|
});
|
|
25
25
|
const prefixed = prefixFunctions('openai', ext);
|
|
26
26
|
const { dispose, ...functions } = prefixed;
|
|
@@ -36,134 +36,15 @@ const result = await execute(parse(script), ctx);
|
|
|
36
36
|
dispose?.();
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
All functions return a dict with `content`, `model`, `usage`, `stop_reason`, `id`, and `messages`.
|
|
42
|
-
|
|
43
|
-
### openai::message(text, options?)
|
|
44
|
-
|
|
45
|
-
Send a single message to OpenAI.
|
|
46
|
-
|
|
47
|
-
```rill
|
|
48
|
-
openai::message("Analyze this code for security issues") => $response
|
|
49
|
-
$response.content -> log
|
|
50
|
-
$response.usage.output -> log
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### openai::messages(messages, options?)
|
|
54
|
-
|
|
55
|
-
Send a multi-turn conversation.
|
|
56
|
-
|
|
57
|
-
```rill
|
|
58
|
-
openai::messages([
|
|
59
|
-
[role: "user", content: "What is rill?"],
|
|
60
|
-
[role: "assistant", content: "A scripting language for AI agents."],
|
|
61
|
-
[role: "user", content: "Show me an example."]
|
|
62
|
-
]) => $response
|
|
63
|
-
$response.content -> log
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### openai::embed(text)
|
|
67
|
-
|
|
68
|
-
Generate an embedding vector for text. Requires `embed_model` in config.
|
|
69
|
-
|
|
70
|
-
```rill
|
|
71
|
-
openai::embed("Hello world") => $vector
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### openai::embed_batch(texts)
|
|
75
|
-
|
|
76
|
-
Generate embedding vectors for multiple texts in a single API call.
|
|
77
|
-
|
|
78
|
-
```rill
|
|
79
|
-
openai::embed_batch(["Hello", "World"]) => $vectors
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### openai::tool_loop(prompt, options)
|
|
83
|
-
|
|
84
|
-
Execute a tool-use loop where the model calls rill functions iteratively.
|
|
85
|
-
|
|
86
|
-
```rill
|
|
87
|
-
openai::tool_loop("Find the weather", [tools: $my_tools]) => $result
|
|
88
|
-
$result.content -> log
|
|
89
|
-
$result.turns -> log
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Configuration
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
const ext = createOpenAIExtension({
|
|
96
|
-
api_key: process.env.OPENAI_API_KEY!,
|
|
97
|
-
model: 'gpt-4-turbo',
|
|
98
|
-
temperature: 0.7,
|
|
99
|
-
base_url: 'http://localhost:1234/v1', // OpenAI-compatible server
|
|
100
|
-
max_tokens: 4096,
|
|
101
|
-
max_retries: 3,
|
|
102
|
-
timeout: 30000,
|
|
103
|
-
system: 'You are a helpful assistant.',
|
|
104
|
-
embed_model: 'text-embedding-3-small',
|
|
105
|
-
});
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
| Option | Type | Default | Description |
|
|
109
|
-
|--------|------|---------|-------------|
|
|
110
|
-
| `api_key` | string | required | OpenAI API key |
|
|
111
|
-
| `model` | string | required | Model identifier |
|
|
112
|
-
| `temperature` | number | undefined | Temperature (0.0-2.0) |
|
|
113
|
-
| `base_url` | string | undefined | Custom API endpoint URL |
|
|
114
|
-
| `max_tokens` | number | `4096` | Max tokens in response |
|
|
115
|
-
| `max_retries` | number | undefined | Max retry attempts |
|
|
116
|
-
| `timeout` | number | undefined | Request timeout in ms |
|
|
117
|
-
| `system` | string | undefined | Default system prompt |
|
|
118
|
-
| `embed_model` | string | undefined | Embedding model identifier |
|
|
119
|
-
|
|
120
|
-
## Result Shape
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
interface OpenAIResult {
|
|
124
|
-
content: string; // response text
|
|
125
|
-
model: string; // model used
|
|
126
|
-
usage: {
|
|
127
|
-
input: number; // prompt tokens
|
|
128
|
-
output: number; // completion tokens
|
|
129
|
-
};
|
|
130
|
-
stop_reason: string; // finish reason
|
|
131
|
-
id: string; // request ID
|
|
132
|
-
messages: Array<{ // full conversation history
|
|
133
|
-
role: string;
|
|
134
|
-
content: string;
|
|
135
|
-
}>;
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Lifecycle
|
|
140
|
-
|
|
141
|
-
Call `dispose()` on the extension to cancel pending requests:
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
const ext = createOpenAIExtension({ ... });
|
|
145
|
-
// ... use extension ...
|
|
146
|
-
await ext.dispose?.();
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Test Host
|
|
150
|
-
|
|
151
|
-
A runnable example at `examples/test-host.ts` wires up the extension with the rill runtime:
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
pnpm exec tsx examples/test-host.ts
|
|
155
|
-
pnpm exec tsx examples/test-host.ts -e 'openai::message("Tell me a joke") -> log'
|
|
156
|
-
pnpm exec tsx examples/test-host.ts script.rill
|
|
157
|
-
```
|
|
39
|
+
## Documentation
|
|
158
40
|
|
|
159
|
-
|
|
41
|
+
See [full documentation](docs/extension-llm-openai.md) for configuration, functions, error handling, events, and examples.
|
|
160
42
|
|
|
161
|
-
##
|
|
43
|
+
## Related
|
|
162
44
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| [Host API Reference](https://github.com/rcrsr/rill/blob/main/docs/ref-host-api.md) | Runtime context and host functions |
|
|
45
|
+
- [rill](https://github.com/rcrsr/rill) — Core language runtime
|
|
46
|
+
- [Extensions Guide](https://github.com/rcrsr/rill/blob/main/docs/integration-extensions.md) — Extension contract and patterns
|
|
47
|
+
- [Host API Reference](https://github.com/rcrsr/rill/blob/main/docs/ref-host-api.md) — Runtime context and host functions
|
|
167
48
|
|
|
168
49
|
## License
|
|
169
50
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
-
import { ExtensionResult } from '@rcrsr/rill';
|
|
3
|
+
import { ExtensionConfigSchema, ExtensionResult } from '@rcrsr/rill';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Base configuration for LLM extensions
|
|
@@ -73,12 +73,8 @@ export type OpenAIExtensionConfig = LLMProviderConfig;
|
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
75
|
export declare function createOpenAIExtension(config: OpenAIExtensionConfig): ExtensionResult;
|
|
76
|
-
/**
|
|
77
|
-
* @rcrsr/rill-ext-openai
|
|
78
|
-
*
|
|
79
|
-
* Extension for OpenAI API integration with rill scripts.
|
|
80
|
-
*/
|
|
81
76
|
export declare const VERSION = "0.0.1";
|
|
77
|
+
export declare const configSchema: ExtensionConfigSchema;
|
|
82
78
|
|
|
83
79
|
export {
|
|
84
80
|
LLMProviderConfig as LLMExtensionConfig,
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// src/factory.ts
|
|
2
2
|
import OpenAI from "openai";
|
|
3
3
|
import {
|
|
4
|
-
RuntimeError as
|
|
4
|
+
RuntimeError as RuntimeError5,
|
|
5
5
|
emitExtensionEvent,
|
|
6
6
|
createVector,
|
|
7
|
-
|
|
7
|
+
isDict as isDict2,
|
|
8
8
|
isVector
|
|
9
9
|
} from "@rcrsr/rill";
|
|
10
10
|
|
|
@@ -76,29 +76,108 @@ function mapProviderError(providerName, error, detect) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// ../../shared/ext-llm/dist/tool-loop.js
|
|
79
|
-
import { isCallable, isDict, RuntimeError as
|
|
79
|
+
import { invokeCallable, isCallable, isDict, isRuntimeCallable, RuntimeError as RuntimeError4 } from "@rcrsr/rill";
|
|
80
|
+
|
|
81
|
+
// ../../shared/ext-llm/dist/schema.js
|
|
82
|
+
import { RuntimeError as RuntimeError3 } from "@rcrsr/rill";
|
|
83
|
+
var RILL_TYPE_MAP = {
|
|
84
|
+
string: "string",
|
|
85
|
+
number: "number",
|
|
86
|
+
bool: "boolean",
|
|
87
|
+
list: "array",
|
|
88
|
+
dict: "object",
|
|
89
|
+
vector: "object",
|
|
90
|
+
shape: "object"
|
|
91
|
+
};
|
|
92
|
+
function mapRillType(rillType) {
|
|
93
|
+
const jsonType = RILL_TYPE_MAP[rillType];
|
|
94
|
+
if (jsonType === void 0) {
|
|
95
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${rillType}`);
|
|
96
|
+
}
|
|
97
|
+
return jsonType;
|
|
98
|
+
}
|
|
99
|
+
function buildJsonSchema(rillSchema) {
|
|
100
|
+
const properties = {};
|
|
101
|
+
const required = [];
|
|
102
|
+
for (const [key, value] of Object.entries(rillSchema)) {
|
|
103
|
+
if (typeof value === "string") {
|
|
104
|
+
properties[key] = buildProperty(value);
|
|
105
|
+
} else if (typeof value === "object" && value !== null) {
|
|
106
|
+
properties[key] = buildProperty(value);
|
|
107
|
+
} else {
|
|
108
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${String(value)}`);
|
|
109
|
+
}
|
|
110
|
+
required.push(key);
|
|
111
|
+
}
|
|
112
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
113
|
+
}
|
|
114
|
+
function buildProperty(descriptor) {
|
|
115
|
+
if (typeof descriptor === "string") {
|
|
116
|
+
const jsonType2 = mapRillType(descriptor);
|
|
117
|
+
return { type: jsonType2 };
|
|
118
|
+
}
|
|
119
|
+
const rillType = descriptor["type"];
|
|
120
|
+
if (typeof rillType !== "string") {
|
|
121
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${String(rillType)}`);
|
|
122
|
+
}
|
|
123
|
+
const jsonType = mapRillType(rillType);
|
|
124
|
+
const property = { type: jsonType };
|
|
125
|
+
const description = descriptor["description"];
|
|
126
|
+
if (typeof description === "string") {
|
|
127
|
+
property.description = description;
|
|
128
|
+
}
|
|
129
|
+
if ("enum" in descriptor) {
|
|
130
|
+
if (rillType !== "string") {
|
|
131
|
+
throw new RuntimeError3("RILL-R004", "enum is only valid for string type");
|
|
132
|
+
}
|
|
133
|
+
const enumValues = descriptor["enum"];
|
|
134
|
+
if (Array.isArray(enumValues)) {
|
|
135
|
+
property.enum = enumValues;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (rillType === "list" && "items" in descriptor) {
|
|
139
|
+
const items = descriptor["items"];
|
|
140
|
+
if (typeof items === "string") {
|
|
141
|
+
property.items = buildProperty(items);
|
|
142
|
+
} else if (typeof items === "object" && items !== null) {
|
|
143
|
+
property.items = buildProperty(items);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (rillType === "dict" && "properties" in descriptor) {
|
|
147
|
+
const nestedProps = descriptor["properties"];
|
|
148
|
+
if (typeof nestedProps === "object" && nestedProps !== null) {
|
|
149
|
+
const subSchema = buildJsonSchema(nestedProps);
|
|
150
|
+
property.properties = subSchema.properties;
|
|
151
|
+
property.required = subSchema.required;
|
|
152
|
+
property.additionalProperties = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return property;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ../../shared/ext-llm/dist/tool-loop.js
|
|
80
159
|
async function executeToolCall(toolName, toolInput, tools, context) {
|
|
81
160
|
if (!isDict(tools)) {
|
|
82
|
-
throw new
|
|
161
|
+
throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
|
|
83
162
|
}
|
|
84
163
|
const toolsDict = tools;
|
|
85
164
|
const toolFn = toolsDict[toolName];
|
|
86
165
|
if (toolFn === void 0 || toolFn === null) {
|
|
87
|
-
throw new
|
|
166
|
+
throw new RuntimeError4("RILL-R004", `Unknown tool: ${toolName}`);
|
|
88
167
|
}
|
|
89
168
|
if (!isCallable(toolFn)) {
|
|
90
|
-
throw new
|
|
169
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be callable`);
|
|
91
170
|
}
|
|
92
171
|
if (typeof toolInput !== "object" || toolInput === null) {
|
|
93
|
-
throw new
|
|
172
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: input must be an object`);
|
|
94
173
|
}
|
|
95
174
|
const callable = toolFn;
|
|
96
|
-
if (callable.kind !== "runtime" && callable.kind !== "application") {
|
|
97
|
-
throw new
|
|
175
|
+
if (callable.kind !== "runtime" && callable.kind !== "application" && callable.kind !== "script") {
|
|
176
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
|
|
98
177
|
}
|
|
99
178
|
try {
|
|
100
179
|
let args;
|
|
101
|
-
if (callable.kind === "application" && callable.params) {
|
|
180
|
+
if ((callable.kind === "application" || callable.kind === "script") && callable.params && callable.params.length > 0) {
|
|
102
181
|
const params = callable.params;
|
|
103
182
|
const inputDict = toolInput;
|
|
104
183
|
args = params.map((param) => {
|
|
@@ -108,6 +187,12 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
108
187
|
} else {
|
|
109
188
|
args = [toolInput];
|
|
110
189
|
}
|
|
190
|
+
if (callable.kind === "script") {
|
|
191
|
+
if (!context) {
|
|
192
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
|
|
193
|
+
}
|
|
194
|
+
return await invokeCallable(callable, args, context);
|
|
195
|
+
}
|
|
111
196
|
const ctx = context ?? {
|
|
112
197
|
parent: void 0,
|
|
113
198
|
variables: /* @__PURE__ */ new Map(),
|
|
@@ -116,77 +201,146 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
116
201
|
const result = callable.fn(args, ctx);
|
|
117
202
|
return result instanceof Promise ? await result : result;
|
|
118
203
|
} catch (error) {
|
|
119
|
-
if (error instanceof
|
|
204
|
+
if (error instanceof RuntimeError4) {
|
|
120
205
|
throw error;
|
|
121
206
|
}
|
|
122
207
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
123
|
-
throw new
|
|
208
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: ${message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function sanitizeToolName(name) {
|
|
212
|
+
const match = name.match(/^[a-zA-Z0-9_-]*/);
|
|
213
|
+
const sanitized = match ? match[0] : "";
|
|
214
|
+
return sanitized.length > 0 ? sanitized : name;
|
|
215
|
+
}
|
|
216
|
+
function patchResponseToolCallNames(response, nameMap) {
|
|
217
|
+
if (!nameMap.size || !response || typeof response !== "object")
|
|
218
|
+
return;
|
|
219
|
+
const resp = response;
|
|
220
|
+
if (Array.isArray(resp["choices"])) {
|
|
221
|
+
for (const choice of resp["choices"]) {
|
|
222
|
+
const msg = choice?.["message"];
|
|
223
|
+
const tcs = msg?.["tool_calls"];
|
|
224
|
+
if (Array.isArray(tcs)) {
|
|
225
|
+
for (const tc of tcs) {
|
|
226
|
+
const fn = tc?.["function"];
|
|
227
|
+
if (fn && typeof fn["name"] === "string") {
|
|
228
|
+
const orig = fn["name"];
|
|
229
|
+
const san = nameMap.get(orig);
|
|
230
|
+
if (san !== void 0)
|
|
231
|
+
fn["name"] = san;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(resp["content"])) {
|
|
238
|
+
for (const block of resp["content"]) {
|
|
239
|
+
const b = block;
|
|
240
|
+
if (b?.["type"] === "tool_use" && typeof b?.["name"] === "string") {
|
|
241
|
+
const orig = b["name"];
|
|
242
|
+
const san = nameMap.get(orig);
|
|
243
|
+
if (san !== void 0)
|
|
244
|
+
b["name"] = san;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(resp["functionCalls"])) {
|
|
249
|
+
for (const fc of resp["functionCalls"]) {
|
|
250
|
+
const f = fc;
|
|
251
|
+
if (typeof f?.["name"] === "string") {
|
|
252
|
+
const orig = f["name"];
|
|
253
|
+
const san = nameMap.get(orig);
|
|
254
|
+
if (san !== void 0)
|
|
255
|
+
f["name"] = san;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(resp["candidates"])) {
|
|
260
|
+
for (const cand of resp["candidates"]) {
|
|
261
|
+
const content = cand?.["content"];
|
|
262
|
+
const parts = content?.["parts"];
|
|
263
|
+
if (Array.isArray(parts)) {
|
|
264
|
+
for (const part of parts) {
|
|
265
|
+
const fc = part?.["functionCall"];
|
|
266
|
+
if (fc && typeof fc["name"] === "string") {
|
|
267
|
+
const orig = fc["name"];
|
|
268
|
+
const san = nameMap.get(orig);
|
|
269
|
+
if (san !== void 0)
|
|
270
|
+
fc["name"] = san;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
124
275
|
}
|
|
125
276
|
}
|
|
126
277
|
async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context) {
|
|
127
278
|
if (tools === void 0) {
|
|
128
|
-
throw new
|
|
279
|
+
throw new RuntimeError4("RILL-R004", "tools parameter is required");
|
|
129
280
|
}
|
|
130
281
|
if (!isDict(tools)) {
|
|
131
|
-
throw new
|
|
282
|
+
throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
|
|
132
283
|
}
|
|
133
284
|
const toolsDict = tools;
|
|
134
285
|
const toolDescriptors = Object.entries(toolsDict).map(([name, fn]) => {
|
|
135
286
|
const fnValue = fn;
|
|
287
|
+
if (isRuntimeCallable(fnValue)) {
|
|
288
|
+
throw new RuntimeError4("RILL-R004", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
|
|
289
|
+
}
|
|
136
290
|
if (!isCallable(fnValue)) {
|
|
137
|
-
throw new
|
|
291
|
+
throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
|
|
138
292
|
}
|
|
139
293
|
const callable = fnValue;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
294
|
+
let description;
|
|
295
|
+
if (callable.kind === "script") {
|
|
296
|
+
description = callable.annotations["description"] ?? "";
|
|
297
|
+
} else {
|
|
298
|
+
description = callable.description ?? "";
|
|
299
|
+
}
|
|
300
|
+
let inputSchema;
|
|
301
|
+
const params = callable.kind === "application" ? callable.params ?? [] : callable.kind === "script" ? callable.params : [];
|
|
302
|
+
if (params.length > 0) {
|
|
303
|
+
const properties = {};
|
|
304
|
+
const required = [];
|
|
305
|
+
for (const param of params) {
|
|
306
|
+
const property = {};
|
|
307
|
+
if (param.typeName !== null) {
|
|
308
|
+
const descriptor = {
|
|
309
|
+
[param.name]: { type: param.typeName }
|
|
310
|
+
};
|
|
311
|
+
const schema = buildJsonSchema(descriptor);
|
|
312
|
+
const built = schema.properties[param.name];
|
|
313
|
+
if (built !== void 0) {
|
|
314
|
+
Object.assign(property, built);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
let paramDesc;
|
|
318
|
+
if (callable.kind === "script") {
|
|
319
|
+
const annot = callable.paramAnnotations[param.name];
|
|
320
|
+
paramDesc = annot?.["description"] ?? "";
|
|
321
|
+
} else {
|
|
322
|
+
paramDesc = param.description ?? "";
|
|
169
323
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
};
|
|
173
|
-
if (param.description) {
|
|
174
|
-
property["description"] = param.description;
|
|
324
|
+
if (paramDesc) {
|
|
325
|
+
property["description"] = paramDesc;
|
|
175
326
|
}
|
|
176
327
|
properties[param.name] = property;
|
|
177
328
|
if (param.defaultValue === null) {
|
|
178
329
|
required.push(param.name);
|
|
179
330
|
}
|
|
180
331
|
}
|
|
332
|
+
inputSchema = {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties,
|
|
335
|
+
required
|
|
336
|
+
};
|
|
337
|
+
} else {
|
|
338
|
+
inputSchema = { type: "object", properties: {}, required: [] };
|
|
181
339
|
}
|
|
182
340
|
return {
|
|
183
341
|
name,
|
|
184
342
|
description,
|
|
185
|
-
input_schema:
|
|
186
|
-
type: "object",
|
|
187
|
-
properties,
|
|
188
|
-
required
|
|
189
|
-
}
|
|
343
|
+
input_schema: inputSchema
|
|
190
344
|
};
|
|
191
345
|
});
|
|
192
346
|
const providerTools = callbacks.buildTools(toolDescriptors);
|
|
@@ -203,7 +357,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
203
357
|
response = await callbacks.callAPI(currentMessages, providerTools);
|
|
204
358
|
} catch (error) {
|
|
205
359
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
206
|
-
throw new
|
|
360
|
+
throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
|
|
207
361
|
}
|
|
208
362
|
if (typeof response === "object" && response !== null && "usage" in response) {
|
|
209
363
|
const usage = response["usage"];
|
|
@@ -215,7 +369,17 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
215
369
|
totalOutputTokens += outputTokens;
|
|
216
370
|
}
|
|
217
371
|
}
|
|
218
|
-
const
|
|
372
|
+
const rawToolCalls = callbacks.extractToolCalls(response);
|
|
373
|
+
const nameMap = /* @__PURE__ */ new Map();
|
|
374
|
+
const toolCalls = rawToolCalls?.map((tc) => {
|
|
375
|
+
const sanitized = sanitizeToolName(tc.name);
|
|
376
|
+
if (sanitized !== tc.name)
|
|
377
|
+
nameMap.set(tc.name, sanitized);
|
|
378
|
+
return sanitized !== tc.name ? { ...tc, name: sanitized } : tc;
|
|
379
|
+
}) ?? null;
|
|
380
|
+
if (nameMap.size > 0) {
|
|
381
|
+
patchResponseToolCallNames(response, nameMap);
|
|
382
|
+
}
|
|
219
383
|
if (toolCalls === null || toolCalls.length === 0) {
|
|
220
384
|
return {
|
|
221
385
|
response,
|
|
@@ -240,7 +404,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
240
404
|
const duration = Date.now() - toolStartTime;
|
|
241
405
|
consecutiveErrors++;
|
|
242
406
|
let originalError;
|
|
243
|
-
if (error instanceof
|
|
407
|
+
if (error instanceof RuntimeError4) {
|
|
244
408
|
const prefix = `Invalid tool input for ${name}: `;
|
|
245
409
|
if (error.message.startsWith(prefix)) {
|
|
246
410
|
originalError = error.message.slice(prefix.length);
|
|
@@ -265,12 +429,20 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
265
429
|
duration
|
|
266
430
|
});
|
|
267
431
|
if (consecutiveErrors >= maxErrors) {
|
|
268
|
-
throw new
|
|
432
|
+
throw new RuntimeError4("RILL-R004", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
|
|
269
433
|
}
|
|
270
434
|
}
|
|
271
435
|
}
|
|
436
|
+
const assistantMessage = callbacks.formatAssistantMessage(response);
|
|
437
|
+
if (assistantMessage != null) {
|
|
438
|
+
currentMessages.push(assistantMessage);
|
|
439
|
+
}
|
|
272
440
|
const toolResultMessage = callbacks.formatToolResult(toolResults);
|
|
273
|
-
|
|
441
|
+
if (Array.isArray(toolResultMessage)) {
|
|
442
|
+
currentMessages.push(...toolResultMessage);
|
|
443
|
+
} else {
|
|
444
|
+
currentMessages.push(toolResultMessage);
|
|
445
|
+
}
|
|
274
446
|
}
|
|
275
447
|
return {
|
|
276
448
|
response: null,
|
|
@@ -281,7 +453,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
281
453
|
}
|
|
282
454
|
|
|
283
455
|
// src/factory.ts
|
|
284
|
-
var
|
|
456
|
+
var DEFAULT_MAX_COMPLETION_TOKENS = 4096;
|
|
285
457
|
var detectOpenAIError = (error) => {
|
|
286
458
|
if (error instanceof OpenAI.APIError) {
|
|
287
459
|
return {
|
|
@@ -303,7 +475,7 @@ function createOpenAIExtension(config) {
|
|
|
303
475
|
});
|
|
304
476
|
const factoryModel = config.model;
|
|
305
477
|
const factoryTemperature = config.temperature;
|
|
306
|
-
const factoryMaxTokens = config.max_tokens ??
|
|
478
|
+
const factoryMaxTokens = config.max_tokens ?? DEFAULT_MAX_COMPLETION_TOKENS;
|
|
307
479
|
const factorySystem = config.system;
|
|
308
480
|
const factoryEmbedModel = config.embed_model;
|
|
309
481
|
void factoryEmbedModel;
|
|
@@ -337,7 +509,7 @@ function createOpenAIExtension(config) {
|
|
|
337
509
|
const text = args[0];
|
|
338
510
|
const options = args[1] ?? {};
|
|
339
511
|
if (text.trim().length === 0) {
|
|
340
|
-
throw new
|
|
512
|
+
throw new RuntimeError5("RILL-R004", "prompt text cannot be empty");
|
|
341
513
|
}
|
|
342
514
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
343
515
|
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
@@ -354,7 +526,7 @@ function createOpenAIExtension(config) {
|
|
|
354
526
|
});
|
|
355
527
|
const apiParams = {
|
|
356
528
|
model: factoryModel,
|
|
357
|
-
|
|
529
|
+
max_completion_tokens: maxTokens,
|
|
358
530
|
messages: apiMessages
|
|
359
531
|
};
|
|
360
532
|
if (factoryTemperature !== void 0) {
|
|
@@ -383,7 +555,9 @@ function createOpenAIExtension(config) {
|
|
|
383
555
|
subsystem: "extension:openai",
|
|
384
556
|
duration,
|
|
385
557
|
model: response.model,
|
|
386
|
-
usage: result2.usage
|
|
558
|
+
usage: result2.usage,
|
|
559
|
+
request: apiMessages,
|
|
560
|
+
content
|
|
387
561
|
});
|
|
388
562
|
return result2;
|
|
389
563
|
} catch (error) {
|
|
@@ -417,7 +591,7 @@ function createOpenAIExtension(config) {
|
|
|
417
591
|
const messages = args[0];
|
|
418
592
|
const options = args[1] ?? {};
|
|
419
593
|
if (messages.length === 0) {
|
|
420
|
-
throw new
|
|
594
|
+
throw new RuntimeError5(
|
|
421
595
|
"RILL-R004",
|
|
422
596
|
"messages list cannot be empty"
|
|
423
597
|
);
|
|
@@ -434,18 +608,18 @@ function createOpenAIExtension(config) {
|
|
|
434
608
|
for (let i = 0; i < messages.length; i++) {
|
|
435
609
|
const msg = messages[i];
|
|
436
610
|
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
437
|
-
throw new
|
|
611
|
+
throw new RuntimeError5(
|
|
438
612
|
"RILL-R004",
|
|
439
613
|
"message missing required 'role' field"
|
|
440
614
|
);
|
|
441
615
|
}
|
|
442
616
|
const role = msg["role"];
|
|
443
617
|
if (role !== "user" && role !== "assistant" && role !== "tool") {
|
|
444
|
-
throw new
|
|
618
|
+
throw new RuntimeError5("RILL-R004", `invalid role '${role}'`);
|
|
445
619
|
}
|
|
446
620
|
if (role === "user" || role === "tool") {
|
|
447
621
|
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
448
|
-
throw new
|
|
622
|
+
throw new RuntimeError5(
|
|
449
623
|
"RILL-R004",
|
|
450
624
|
`${role} message requires 'content'`
|
|
451
625
|
);
|
|
@@ -458,7 +632,7 @@ function createOpenAIExtension(config) {
|
|
|
458
632
|
const hasContent = "content" in msg && msg["content"];
|
|
459
633
|
const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
|
|
460
634
|
if (!hasContent && !hasToolCalls) {
|
|
461
|
-
throw new
|
|
635
|
+
throw new RuntimeError5(
|
|
462
636
|
"RILL-R004",
|
|
463
637
|
"assistant message requires 'content' or 'tool_calls'"
|
|
464
638
|
);
|
|
@@ -473,7 +647,7 @@ function createOpenAIExtension(config) {
|
|
|
473
647
|
}
|
|
474
648
|
const apiParams = {
|
|
475
649
|
model: factoryModel,
|
|
476
|
-
|
|
650
|
+
max_completion_tokens: maxTokens,
|
|
477
651
|
messages: apiMessages
|
|
478
652
|
};
|
|
479
653
|
if (factoryTemperature !== void 0) {
|
|
@@ -507,7 +681,9 @@ function createOpenAIExtension(config) {
|
|
|
507
681
|
subsystem: "extension:openai",
|
|
508
682
|
duration,
|
|
509
683
|
model: response.model,
|
|
510
|
-
usage: result2.usage
|
|
684
|
+
usage: result2.usage,
|
|
685
|
+
request: apiMessages,
|
|
686
|
+
content
|
|
511
687
|
});
|
|
512
688
|
return result2;
|
|
513
689
|
} catch (error) {
|
|
@@ -545,7 +721,7 @@ function createOpenAIExtension(config) {
|
|
|
545
721
|
});
|
|
546
722
|
const embeddingData = response.data[0]?.embedding;
|
|
547
723
|
if (!embeddingData || embeddingData.length === 0) {
|
|
548
|
-
throw new
|
|
724
|
+
throw new RuntimeError5(
|
|
549
725
|
"RILL-R004",
|
|
550
726
|
"OpenAI: empty embedding returned"
|
|
551
727
|
);
|
|
@@ -601,7 +777,7 @@ function createOpenAIExtension(config) {
|
|
|
601
777
|
for (const embeddingItem of response.data) {
|
|
602
778
|
const embeddingData = embeddingItem.embedding;
|
|
603
779
|
if (!embeddingData || embeddingData.length === 0) {
|
|
604
|
-
throw new
|
|
780
|
+
throw new RuntimeError5(
|
|
605
781
|
"RILL-R004",
|
|
606
782
|
"OpenAI: empty embedding returned"
|
|
607
783
|
);
|
|
@@ -653,79 +829,14 @@ function createOpenAIExtension(config) {
|
|
|
653
829
|
const prompt = args[0];
|
|
654
830
|
const options = args[1] ?? {};
|
|
655
831
|
if (prompt.trim().length === 0) {
|
|
656
|
-
throw new
|
|
832
|
+
throw new RuntimeError5("RILL-R004", "prompt text cannot be empty");
|
|
657
833
|
}
|
|
658
|
-
if (!("tools" in options) || !
|
|
659
|
-
throw new
|
|
834
|
+
if (!("tools" in options) || !isDict2(options["tools"])) {
|
|
835
|
+
throw new RuntimeError5(
|
|
660
836
|
"RILL-R004",
|
|
661
837
|
"tool_loop requires 'tools' option"
|
|
662
838
|
);
|
|
663
839
|
}
|
|
664
|
-
const toolDescriptors = options["tools"];
|
|
665
|
-
const toolsDict = {};
|
|
666
|
-
for (const descriptor of toolDescriptors) {
|
|
667
|
-
const name = typeof descriptor["name"] === "string" ? descriptor["name"] : null;
|
|
668
|
-
if (!name) {
|
|
669
|
-
throw new RuntimeError4(
|
|
670
|
-
"RILL-R004",
|
|
671
|
-
"tool descriptor missing name"
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
|
-
const toolFnValue = descriptor["fn"];
|
|
675
|
-
if (!toolFnValue) {
|
|
676
|
-
throw new RuntimeError4(
|
|
677
|
-
"RILL-R004",
|
|
678
|
-
`tool '${name}' missing fn property`
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
if (!isCallable2(toolFnValue)) {
|
|
682
|
-
throw new RuntimeError4(
|
|
683
|
-
"RILL-R004",
|
|
684
|
-
`tool '${name}' fn must be callable`
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
const paramsObj = descriptor["params"];
|
|
688
|
-
const description = typeof descriptor["description"] === "string" ? descriptor["description"] : "";
|
|
689
|
-
let enhancedCallable = toolFnValue;
|
|
690
|
-
if (paramsObj && typeof paramsObj === "object" && !Array.isArray(paramsObj)) {
|
|
691
|
-
const params = Object.entries(
|
|
692
|
-
paramsObj
|
|
693
|
-
).map(([paramName, paramMeta]) => {
|
|
694
|
-
const meta = paramMeta;
|
|
695
|
-
const typeStr = typeof meta["type"] === "string" ? meta["type"] : null;
|
|
696
|
-
let typeName = null;
|
|
697
|
-
if (typeStr === "string") typeName = "string";
|
|
698
|
-
else if (typeStr === "number") typeName = "number";
|
|
699
|
-
else if (typeStr === "bool" || typeStr === "boolean")
|
|
700
|
-
typeName = "bool";
|
|
701
|
-
else if (typeStr === "list" || typeStr === "array")
|
|
702
|
-
typeName = "list";
|
|
703
|
-
else if (typeStr === "dict" || typeStr === "object")
|
|
704
|
-
typeName = "dict";
|
|
705
|
-
else if (typeStr === "vector") typeName = "vector";
|
|
706
|
-
const param = {
|
|
707
|
-
name: paramName,
|
|
708
|
-
typeName,
|
|
709
|
-
defaultValue: null,
|
|
710
|
-
annotations: {}
|
|
711
|
-
};
|
|
712
|
-
if (typeof meta["description"] === "string") {
|
|
713
|
-
param.description = meta["description"];
|
|
714
|
-
}
|
|
715
|
-
return param;
|
|
716
|
-
});
|
|
717
|
-
const baseCallable = toolFnValue;
|
|
718
|
-
enhancedCallable = {
|
|
719
|
-
__type: "callable",
|
|
720
|
-
kind: "application",
|
|
721
|
-
params,
|
|
722
|
-
fn: baseCallable.fn,
|
|
723
|
-
description,
|
|
724
|
-
isProperty: baseCallable.isProperty ?? false
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
toolsDict[name] = enhancedCallable;
|
|
728
|
-
}
|
|
729
840
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
730
841
|
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
731
842
|
const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
|
|
@@ -741,17 +852,17 @@ function createOpenAIExtension(config) {
|
|
|
741
852
|
const prependedMessages = options["messages"];
|
|
742
853
|
for (const msg of prependedMessages) {
|
|
743
854
|
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
744
|
-
throw new
|
|
855
|
+
throw new RuntimeError5(
|
|
745
856
|
"RILL-R004",
|
|
746
857
|
"message missing required 'role' field"
|
|
747
858
|
);
|
|
748
859
|
}
|
|
749
860
|
const role = msg["role"];
|
|
750
861
|
if (role !== "user" && role !== "assistant") {
|
|
751
|
-
throw new
|
|
862
|
+
throw new RuntimeError5("RILL-R004", `invalid role '${role}'`);
|
|
752
863
|
}
|
|
753
864
|
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
754
|
-
throw new
|
|
865
|
+
throw new RuntimeError5(
|
|
755
866
|
"RILL-R004",
|
|
756
867
|
`${role} message requires 'content'`
|
|
757
868
|
);
|
|
@@ -782,7 +893,7 @@ function createOpenAIExtension(config) {
|
|
|
782
893
|
callAPI: async (msgs, tools) => {
|
|
783
894
|
const apiParams = {
|
|
784
895
|
model: factoryModel,
|
|
785
|
-
|
|
896
|
+
max_completion_tokens: maxTokens,
|
|
786
897
|
messages: msgs,
|
|
787
898
|
tools,
|
|
788
899
|
tool_choice: "auto"
|
|
@@ -839,6 +950,21 @@ function createOpenAIExtension(config) {
|
|
|
839
950
|
};
|
|
840
951
|
});
|
|
841
952
|
},
|
|
953
|
+
// Extract assistant message (with tool_calls) from OpenAI response
|
|
954
|
+
formatAssistantMessage: (response2) => {
|
|
955
|
+
if (!response2 || typeof response2 !== "object" || !("choices" in response2)) {
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
958
|
+
const choices = response2.choices;
|
|
959
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
const choice = choices[0];
|
|
963
|
+
if (!choice || typeof choice !== "object" || !("message" in choice)) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
return choice.message;
|
|
967
|
+
},
|
|
842
968
|
// Format tool results into OpenAI message format
|
|
843
969
|
formatToolResult: (toolResults) => {
|
|
844
970
|
return toolResults.map((tr) => ({
|
|
@@ -850,7 +976,7 @@ function createOpenAIExtension(config) {
|
|
|
850
976
|
};
|
|
851
977
|
const loopResult = await executeToolLoop(
|
|
852
978
|
messages,
|
|
853
|
-
|
|
979
|
+
options["tools"],
|
|
854
980
|
maxErrors,
|
|
855
981
|
callbacks,
|
|
856
982
|
(event, data) => {
|
|
@@ -908,7 +1034,9 @@ function createOpenAIExtension(config) {
|
|
|
908
1034
|
subsystem: "extension:openai",
|
|
909
1035
|
turns: loopResult.turns,
|
|
910
1036
|
total_duration: duration,
|
|
911
|
-
usage: result2.usage
|
|
1037
|
+
usage: result2.usage,
|
|
1038
|
+
request: messages,
|
|
1039
|
+
content
|
|
912
1040
|
});
|
|
913
1041
|
return result2;
|
|
914
1042
|
} catch (error) {
|
|
@@ -929,6 +1057,125 @@ function createOpenAIExtension(config) {
|
|
|
929
1057
|
},
|
|
930
1058
|
description: "Execute tool-use loop with OpenAI API",
|
|
931
1059
|
returnType: "dict"
|
|
1060
|
+
},
|
|
1061
|
+
// IR-3: openai::generate
|
|
1062
|
+
generate: {
|
|
1063
|
+
params: [
|
|
1064
|
+
{ name: "prompt", type: "string" },
|
|
1065
|
+
{ name: "options", type: "dict" }
|
|
1066
|
+
],
|
|
1067
|
+
fn: async (args, ctx) => {
|
|
1068
|
+
const startTime = Date.now();
|
|
1069
|
+
try {
|
|
1070
|
+
const prompt = args[0];
|
|
1071
|
+
const options = args[1] ?? {};
|
|
1072
|
+
if (!("schema" in options) || options["schema"] === null || options["schema"] === void 0) {
|
|
1073
|
+
throw new RuntimeError5(
|
|
1074
|
+
"RILL-R004",
|
|
1075
|
+
"generate requires 'schema' option"
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
const rillSchema = options["schema"];
|
|
1079
|
+
const jsonSchema = buildJsonSchema(rillSchema);
|
|
1080
|
+
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
1081
|
+
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
1082
|
+
const apiMessages = [];
|
|
1083
|
+
if (system !== void 0) {
|
|
1084
|
+
apiMessages.push({
|
|
1085
|
+
role: "system",
|
|
1086
|
+
content: system
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if ("messages" in options && Array.isArray(options["messages"])) {
|
|
1090
|
+
const prependedMessages = options["messages"];
|
|
1091
|
+
for (const msg of prependedMessages) {
|
|
1092
|
+
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
1093
|
+
throw new RuntimeError5(
|
|
1094
|
+
"RILL-R004",
|
|
1095
|
+
"message missing required 'role' field"
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
const role = msg["role"];
|
|
1099
|
+
if (role !== "user" && role !== "assistant") {
|
|
1100
|
+
throw new RuntimeError5("RILL-R004", `invalid role '${role}'`);
|
|
1101
|
+
}
|
|
1102
|
+
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
1103
|
+
throw new RuntimeError5(
|
|
1104
|
+
"RILL-R004",
|
|
1105
|
+
`${role} message requires 'content'`
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
apiMessages.push({
|
|
1109
|
+
role,
|
|
1110
|
+
content: msg["content"]
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
apiMessages.push({ role: "user", content: prompt });
|
|
1115
|
+
const apiParams = {
|
|
1116
|
+
model: factoryModel,
|
|
1117
|
+
max_completion_tokens: maxTokens,
|
|
1118
|
+
messages: apiMessages,
|
|
1119
|
+
response_format: {
|
|
1120
|
+
type: "json_schema",
|
|
1121
|
+
json_schema: {
|
|
1122
|
+
name: "output",
|
|
1123
|
+
schema: jsonSchema,
|
|
1124
|
+
strict: true
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
if (factoryTemperature !== void 0) {
|
|
1129
|
+
apiParams.temperature = factoryTemperature;
|
|
1130
|
+
}
|
|
1131
|
+
const response = await client.chat.completions.create(apiParams);
|
|
1132
|
+
const raw = response.choices[0]?.message?.content ?? "";
|
|
1133
|
+
let data;
|
|
1134
|
+
try {
|
|
1135
|
+
data = JSON.parse(raw);
|
|
1136
|
+
} catch (parseError) {
|
|
1137
|
+
const detail = parseError instanceof Error ? parseError.message : String(parseError);
|
|
1138
|
+
throw new RuntimeError5(
|
|
1139
|
+
"RILL-R004",
|
|
1140
|
+
`generate: failed to parse response JSON: ${detail}`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
const result2 = {
|
|
1144
|
+
data,
|
|
1145
|
+
raw,
|
|
1146
|
+
model: response.model,
|
|
1147
|
+
usage: {
|
|
1148
|
+
input: response.usage?.prompt_tokens ?? 0,
|
|
1149
|
+
output: response.usage?.completion_tokens ?? 0
|
|
1150
|
+
},
|
|
1151
|
+
stop_reason: response.choices[0]?.finish_reason ?? "unknown",
|
|
1152
|
+
id: response.id
|
|
1153
|
+
};
|
|
1154
|
+
const duration = Date.now() - startTime;
|
|
1155
|
+
emitExtensionEvent(ctx, {
|
|
1156
|
+
event: "openai:generate",
|
|
1157
|
+
subsystem: "extension:openai",
|
|
1158
|
+
duration,
|
|
1159
|
+
model: response.model,
|
|
1160
|
+
usage: result2.usage,
|
|
1161
|
+
request: apiMessages,
|
|
1162
|
+
content: raw
|
|
1163
|
+
});
|
|
1164
|
+
return result2;
|
|
1165
|
+
} catch (error) {
|
|
1166
|
+
const duration = Date.now() - startTime;
|
|
1167
|
+
const rillError = error instanceof RuntimeError5 ? error : mapProviderError("OpenAI", error, detectOpenAIError);
|
|
1168
|
+
emitExtensionEvent(ctx, {
|
|
1169
|
+
event: "openai:error",
|
|
1170
|
+
subsystem: "extension:openai",
|
|
1171
|
+
error: rillError.message,
|
|
1172
|
+
duration
|
|
1173
|
+
});
|
|
1174
|
+
throw rillError;
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
description: "Generate structured output from OpenAI API",
|
|
1178
|
+
returnType: "dict"
|
|
932
1179
|
}
|
|
933
1180
|
};
|
|
934
1181
|
result.dispose = dispose;
|
|
@@ -937,7 +1184,19 @@ function createOpenAIExtension(config) {
|
|
|
937
1184
|
|
|
938
1185
|
// src/index.ts
|
|
939
1186
|
var VERSION = "0.0.1";
|
|
1187
|
+
var configSchema = {
|
|
1188
|
+
api_key: { type: "string", required: true, secret: true },
|
|
1189
|
+
model: { type: "string", required: true },
|
|
1190
|
+
base_url: { type: "string" },
|
|
1191
|
+
temperature: { type: "number" },
|
|
1192
|
+
max_tokens: { type: "number" },
|
|
1193
|
+
timeout: { type: "number" },
|
|
1194
|
+
max_retries: { type: "number" },
|
|
1195
|
+
system: { type: "string" },
|
|
1196
|
+
embed_model: { type: "string" }
|
|
1197
|
+
};
|
|
940
1198
|
export {
|
|
941
1199
|
VERSION,
|
|
1200
|
+
configSchema,
|
|
942
1201
|
createOpenAIExtension
|
|
943
1202
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rcrsr/rill-ext-openai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "rill extension for OpenAI API integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Andre Bremer",
|
|
@@ -17,33 +17,33 @@
|
|
|
17
17
|
"scripting"
|
|
18
18
|
],
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@rcrsr/rill": "^0.
|
|
20
|
+
"@rcrsr/rill": "^0.9.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@
|
|
23
|
+
"@rcrsr/rill": "^0.9.0",
|
|
24
|
+
"@types/node": "^25.3.0",
|
|
24
25
|
"dts-bundle-generator": "^9.5.1",
|
|
25
|
-
"tsup": "^8.5.
|
|
26
|
-
"undici-types": "^7.
|
|
27
|
-
"@rcrsr/rill-ext-llm-shared": "^0.0.1"
|
|
28
|
-
"@rcrsr/rill": "^0.8.6"
|
|
26
|
+
"tsup": "^8.5.1",
|
|
27
|
+
"undici-types": "^7.22.0",
|
|
28
|
+
"@rcrsr/rill-ext-llm-shared": "^0.0.1"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/rcrsr/rill.git",
|
|
35
|
+
"url": "git+https://github.com/rcrsr/rill-ext.git",
|
|
36
36
|
"directory": "packages/ext/llm-openai"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://
|
|
38
|
+
"homepage": "https://github.com/rcrsr/rill-ext/tree/main/packages/ext/llm-openai#readme",
|
|
39
39
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/rcrsr/rill/issues"
|
|
40
|
+
"url": "https://github.com/rcrsr/rill-ext/issues"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"openai": "^6.
|
|
46
|
+
"openai": "^6.25.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup && dts-bundle-generator --config dts-bundle-generator.config.cjs",
|