@rcrsr/rill-ext-openai 0.11.0 → 0.18.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 +37 -27
- package/dist/index.d.ts +4 -3
- package/dist/index.js +670 -455
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
|
|
1
4
|
// src/factory.ts
|
|
2
5
|
import OpenAI from "openai";
|
|
3
6
|
import {
|
|
4
7
|
RuntimeError as RuntimeError6,
|
|
8
|
+
createRillStream,
|
|
5
9
|
emitExtensionEvent,
|
|
6
10
|
createVector,
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
isVector,
|
|
12
|
+
structureToTypeValue,
|
|
13
|
+
toCallable
|
|
9
14
|
} from "@rcrsr/rill";
|
|
10
15
|
|
|
11
16
|
// ../../shared/ext-llm/dist/validation.js
|
|
@@ -45,7 +50,7 @@ function validateEmbedBatch(texts) {
|
|
|
45
50
|
if (typeof item !== "string") {
|
|
46
51
|
throw new RuntimeError("RILL-R001", "embed_batch requires list of strings");
|
|
47
52
|
}
|
|
48
|
-
if (item === "") {
|
|
53
|
+
if (item.trim() === "") {
|
|
49
54
|
throw new RuntimeError("RILL-R001", `embed text cannot be empty at index ${i}`);
|
|
50
55
|
}
|
|
51
56
|
validated.push(item);
|
|
@@ -97,31 +102,34 @@ function mapRillType(rillType) {
|
|
|
97
102
|
return jsonType;
|
|
98
103
|
}
|
|
99
104
|
function buildPropertyFromStructuralType(rillType) {
|
|
100
|
-
if (rillType.
|
|
101
|
-
throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.
|
|
105
|
+
if (rillType.kind === "closure" || rillType.kind === "tuple") {
|
|
106
|
+
throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.kind}`);
|
|
102
107
|
}
|
|
103
|
-
if (rillType.
|
|
108
|
+
if (rillType.kind === "any") {
|
|
104
109
|
return {};
|
|
105
110
|
}
|
|
106
|
-
if (rillType.
|
|
111
|
+
if (rillType.kind === "list") {
|
|
112
|
+
const listType = rillType;
|
|
107
113
|
const property = { type: "array" };
|
|
108
|
-
if (
|
|
109
|
-
property.items = buildPropertyFromStructuralType(
|
|
114
|
+
if (listType.element !== void 0) {
|
|
115
|
+
property.items = buildPropertyFromStructuralType(listType.element);
|
|
110
116
|
}
|
|
111
117
|
return property;
|
|
112
118
|
}
|
|
113
|
-
if (rillType.
|
|
119
|
+
if (rillType.kind === "dict") {
|
|
114
120
|
return { type: "object" };
|
|
115
121
|
}
|
|
116
|
-
return { type: mapRillType(rillType.
|
|
122
|
+
return { type: mapRillType(rillType.kind) };
|
|
117
123
|
}
|
|
118
124
|
function buildJsonSchemaFromStructuralType(type, params) {
|
|
119
125
|
const properties = {};
|
|
120
126
|
const required = [];
|
|
121
|
-
if (type.
|
|
127
|
+
if (type.kind === "closure") {
|
|
122
128
|
const closureParams = type.params ?? [];
|
|
123
129
|
for (let i = 0; i < closureParams.length; i++) {
|
|
124
|
-
const
|
|
130
|
+
const fieldDef = closureParams[i];
|
|
131
|
+
const paramName = fieldDef.name ?? `param${i}`;
|
|
132
|
+
const paramType = fieldDef.type;
|
|
125
133
|
const rillParam = params?.[i];
|
|
126
134
|
const property = buildPropertyFromStructuralType(paramType);
|
|
127
135
|
const description = rillParam?.annotations["description"];
|
|
@@ -220,21 +228,20 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
220
228
|
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
|
|
221
229
|
}
|
|
222
230
|
try {
|
|
223
|
-
|
|
224
|
-
if ((callable.kind === "application" || callable.kind === "script") && callable.params && callable.params.length > 0) {
|
|
225
|
-
const params = callable.params;
|
|
226
|
-
const inputDict = toolInput;
|
|
227
|
-
args = params.map((param) => {
|
|
228
|
-
const value = inputDict[param.name];
|
|
229
|
-
return value !== void 0 ? value : void 0;
|
|
230
|
-
});
|
|
231
|
-
} else {
|
|
232
|
-
args = [toolInput];
|
|
233
|
-
}
|
|
231
|
+
const inputDict = toolInput;
|
|
234
232
|
if (callable.kind === "script") {
|
|
235
233
|
if (!context) {
|
|
236
234
|
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
|
|
237
235
|
}
|
|
236
|
+
let args;
|
|
237
|
+
if (callable.params && callable.params.length > 0) {
|
|
238
|
+
args = callable.params.map((param) => {
|
|
239
|
+
const value = inputDict[param.name];
|
|
240
|
+
return value !== void 0 ? value : void 0;
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
args = [];
|
|
244
|
+
}
|
|
238
245
|
return await invokeCallable(callable, args, context);
|
|
239
246
|
}
|
|
240
247
|
const ctx = context ?? {
|
|
@@ -242,7 +249,7 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
242
249
|
variables: /* @__PURE__ */ new Map(),
|
|
243
250
|
pipeValue: null
|
|
244
251
|
};
|
|
245
|
-
const result = callable.fn(
|
|
252
|
+
const result = callable.fn(inputDict, ctx);
|
|
246
253
|
return result instanceof Promise ? await result : result;
|
|
247
254
|
} catch (error) {
|
|
248
255
|
if (error instanceof RuntimeError4) {
|
|
@@ -318,7 +325,7 @@ function patchResponseToolCallNames(response, nameMap) {
|
|
|
318
325
|
}
|
|
319
326
|
}
|
|
320
327
|
}
|
|
321
|
-
async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context) {
|
|
328
|
+
async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context, yieldChunk, signal) {
|
|
322
329
|
if (tools === void 0) {
|
|
323
330
|
throw new RuntimeError4("RILL-R004", "tools parameter is required");
|
|
324
331
|
}
|
|
@@ -335,18 +342,13 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
335
342
|
throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
|
|
336
343
|
}
|
|
337
344
|
const callable = fnValue;
|
|
338
|
-
|
|
339
|
-
if (callable.kind === "script") {
|
|
340
|
-
description = callable.annotations["description"] ?? "";
|
|
341
|
-
} else {
|
|
342
|
-
description = callable.description ?? "";
|
|
343
|
-
}
|
|
345
|
+
const description = callable.annotations?.["description"] ?? "";
|
|
344
346
|
let inputSchema;
|
|
345
347
|
const params = callable.kind === "application" ? callable.params ?? [] : callable.kind === "script" ? callable.params : [];
|
|
346
348
|
if (params.length > 0) {
|
|
347
349
|
const closureType = {
|
|
348
|
-
|
|
349
|
-
params: params.map((p2) =>
|
|
350
|
+
kind: "closure",
|
|
351
|
+
params: params.map((p2) => ({ name: p2.name, type: p2.type ?? { kind: "any" } }))
|
|
350
352
|
};
|
|
351
353
|
const builtSchema = buildJsonSchemaFromStructuralType(closureType, [...params]);
|
|
352
354
|
inputSchema = {
|
|
@@ -371,10 +373,20 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
371
373
|
let currentMessages = [...messages];
|
|
372
374
|
let turnCount = 0;
|
|
373
375
|
while (turnCount < maxTurns) {
|
|
376
|
+
if (signal?.aborted) {
|
|
377
|
+
throw new RuntimeError4("RILL-R004", "tool_loop cancelled");
|
|
378
|
+
}
|
|
374
379
|
turnCount++;
|
|
375
380
|
let response;
|
|
376
381
|
try {
|
|
377
|
-
|
|
382
|
+
if (yieldChunk !== void 0 && callbacks.callAPIStreaming !== void 0) {
|
|
383
|
+
const onTextDelta = (text) => {
|
|
384
|
+
yieldChunk({ type: "text_delta", text });
|
|
385
|
+
};
|
|
386
|
+
response = await callbacks.callAPIStreaming(currentMessages, providerTools, onTextDelta, signal);
|
|
387
|
+
} else {
|
|
388
|
+
response = await callbacks.callAPI(currentMessages, providerTools, signal);
|
|
389
|
+
}
|
|
378
390
|
} catch (error) {
|
|
379
391
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
380
392
|
throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
|
|
@@ -412,6 +424,13 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
412
424
|
for (const toolCall of toolCalls) {
|
|
413
425
|
const { id, name, input } = toolCall;
|
|
414
426
|
emitEvent("tool_call", { tool_name: name, args: input });
|
|
427
|
+
if (yieldChunk !== void 0) {
|
|
428
|
+
yieldChunk({
|
|
429
|
+
type: "tool_call",
|
|
430
|
+
name,
|
|
431
|
+
args: input
|
|
432
|
+
});
|
|
433
|
+
}
|
|
415
434
|
const toolStartTime = Date.now();
|
|
416
435
|
try {
|
|
417
436
|
const result = await executeToolCall(name, input, tools, context);
|
|
@@ -419,6 +438,9 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
419
438
|
toolResults.push({ id, name, result });
|
|
420
439
|
executedToolCalls.push({ name, result });
|
|
421
440
|
consecutiveErrors = 0;
|
|
441
|
+
if (yieldChunk !== void 0) {
|
|
442
|
+
yieldChunk({ type: "tool_result", name, result });
|
|
443
|
+
}
|
|
422
444
|
emitEvent("tool_result", { tool_name: name, duration });
|
|
423
445
|
} catch (error) {
|
|
424
446
|
const duration = Date.now() - toolStartTime;
|
|
@@ -471,6 +493,9 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
471
493
|
turns: turnCount
|
|
472
494
|
};
|
|
473
495
|
}
|
|
496
|
+
function buildResponseMessages(inputMessages, assistantContent) {
|
|
497
|
+
return [...inputMessages, { role: "assistant", content: assistantContent }];
|
|
498
|
+
}
|
|
474
499
|
|
|
475
500
|
// ../../shared/ext-param/dist/param.js
|
|
476
501
|
import { RuntimeError as RuntimeError5 } from "@rcrsr/rill";
|
|
@@ -500,7 +525,7 @@ var p = {
|
|
|
500
525
|
validateParamName(name);
|
|
501
526
|
return {
|
|
502
527
|
name,
|
|
503
|
-
type: {
|
|
528
|
+
type: { kind: "string" },
|
|
504
529
|
defaultValue: void 0,
|
|
505
530
|
annotations: buildAnnotations(desc)
|
|
506
531
|
};
|
|
@@ -517,7 +542,7 @@ var p = {
|
|
|
517
542
|
validateParamName(name);
|
|
518
543
|
return {
|
|
519
544
|
name,
|
|
520
|
-
type: {
|
|
545
|
+
type: { kind: "number" },
|
|
521
546
|
defaultValue: def,
|
|
522
547
|
annotations: buildAnnotations(desc)
|
|
523
548
|
};
|
|
@@ -534,7 +559,7 @@ var p = {
|
|
|
534
559
|
validateParamName(name);
|
|
535
560
|
return {
|
|
536
561
|
name,
|
|
537
|
-
type: {
|
|
562
|
+
type: { kind: "bool" },
|
|
538
563
|
defaultValue: def,
|
|
539
564
|
annotations: buildAnnotations(desc)
|
|
540
565
|
};
|
|
@@ -545,13 +570,15 @@ var p = {
|
|
|
545
570
|
* @param name - Parameter name (must be a valid identifier)
|
|
546
571
|
* @param desc - Optional description
|
|
547
572
|
* @param def - Optional default value
|
|
548
|
-
* @
|
|
573
|
+
* @param fields - Optional structural field definitions (RillFieldDef with type and optional defaultValue)
|
|
574
|
+
* @returns RillParam with type 'dict' (with fields if provided)
|
|
549
575
|
*/
|
|
550
|
-
dict(name, desc, def) {
|
|
576
|
+
dict(name, desc, def, fields) {
|
|
551
577
|
validateParamName(name);
|
|
578
|
+
const type = fields !== void 0 ? { kind: "dict", fields } : { kind: "dict" };
|
|
552
579
|
return {
|
|
553
580
|
name,
|
|
554
|
-
type
|
|
581
|
+
type,
|
|
555
582
|
defaultValue: def,
|
|
556
583
|
annotations: buildAnnotations(desc)
|
|
557
584
|
};
|
|
@@ -566,7 +593,7 @@ var p = {
|
|
|
566
593
|
*/
|
|
567
594
|
list(name, itemType, desc) {
|
|
568
595
|
validateParamName(name);
|
|
569
|
-
const type = itemType !== void 0 ? {
|
|
596
|
+
const type = itemType !== void 0 ? { kind: "list", element: itemType } : { kind: "list" };
|
|
570
597
|
return {
|
|
571
598
|
name,
|
|
572
599
|
type,
|
|
@@ -585,7 +612,7 @@ var p = {
|
|
|
585
612
|
validateParamName(name);
|
|
586
613
|
return {
|
|
587
614
|
name,
|
|
588
|
-
type: {
|
|
615
|
+
type: { kind: "closure" },
|
|
589
616
|
defaultValue: void 0,
|
|
590
617
|
annotations: buildAnnotations(desc)
|
|
591
618
|
};
|
|
@@ -636,214 +663,305 @@ function createOpenAIExtension(config) {
|
|
|
636
663
|
console.warn(`Failed to cleanup OpenAI SDK: ${message}`);
|
|
637
664
|
}
|
|
638
665
|
};
|
|
639
|
-
const
|
|
666
|
+
const fnDict = {
|
|
640
667
|
// IR-4: openai::message
|
|
641
668
|
message: {
|
|
642
669
|
params: [
|
|
643
670
|
p.str("text"),
|
|
644
|
-
p.dict("options", void 0, {}
|
|
671
|
+
p.dict("options", void 0, {}, {
|
|
672
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
673
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 }
|
|
674
|
+
})
|
|
645
675
|
],
|
|
646
|
-
fn:
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const apiMessages = [];
|
|
657
|
-
if (system !== void 0) {
|
|
658
|
-
apiMessages.push({
|
|
659
|
-
role: "system",
|
|
660
|
-
content: system
|
|
661
|
-
});
|
|
662
|
-
}
|
|
676
|
+
fn: (args, ctx) => {
|
|
677
|
+
const text = args["text"];
|
|
678
|
+
const options = args["options"] ?? {};
|
|
679
|
+
if (text.trim().length === 0) {
|
|
680
|
+
throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
|
|
681
|
+
}
|
|
682
|
+
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
683
|
+
const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
|
|
684
|
+
const apiMessages = [];
|
|
685
|
+
if (system !== void 0) {
|
|
663
686
|
apiMessages.push({
|
|
664
|
-
role: "
|
|
665
|
-
content:
|
|
687
|
+
role: "system",
|
|
688
|
+
content: system
|
|
666
689
|
});
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
690
|
+
}
|
|
691
|
+
apiMessages.push({
|
|
692
|
+
role: "user",
|
|
693
|
+
content: text
|
|
694
|
+
});
|
|
695
|
+
const runner = client.chat.completions.stream({
|
|
696
|
+
model: factoryModel,
|
|
697
|
+
max_completion_tokens: maxTokens,
|
|
698
|
+
messages: apiMessages,
|
|
699
|
+
stream_options: { include_usage: true },
|
|
700
|
+
...factoryTemperature !== void 0 ? { temperature: factoryTemperature } : {}
|
|
701
|
+
});
|
|
702
|
+
async function* chunks() {
|
|
703
|
+
try {
|
|
704
|
+
for await (const chunk of runner) {
|
|
705
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
706
|
+
if (delta) {
|
|
707
|
+
yield delta;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
} catch (error) {
|
|
711
|
+
throw mapProviderError("OpenAI", error, detectOpenAIError);
|
|
674
712
|
}
|
|
675
|
-
const response = await client.chat.completions.create(apiParams);
|
|
676
|
-
const content = response.choices[0]?.message?.content ?? "";
|
|
677
|
-
const result2 = {
|
|
678
|
-
content,
|
|
679
|
-
model: response.model,
|
|
680
|
-
usage: {
|
|
681
|
-
input: response.usage?.prompt_tokens ?? 0,
|
|
682
|
-
output: response.usage?.completion_tokens ?? 0
|
|
683
|
-
},
|
|
684
|
-
stop_reason: response.choices[0]?.finish_reason ?? "unknown",
|
|
685
|
-
id: response.id,
|
|
686
|
-
messages: [
|
|
687
|
-
...system ? [{ role: "system", content: system }] : [],
|
|
688
|
-
{ role: "user", content: text },
|
|
689
|
-
{ role: "assistant", content }
|
|
690
|
-
]
|
|
691
|
-
};
|
|
692
|
-
const duration = Date.now() - startTime;
|
|
693
|
-
emitExtensionEvent(ctx, {
|
|
694
|
-
event: "openai:message",
|
|
695
|
-
subsystem: "extension:openai",
|
|
696
|
-
duration,
|
|
697
|
-
model: response.model,
|
|
698
|
-
usage: result2.usage,
|
|
699
|
-
request: apiMessages,
|
|
700
|
-
content
|
|
701
|
-
});
|
|
702
|
-
return result2;
|
|
703
|
-
} catch (error) {
|
|
704
|
-
const duration = Date.now() - startTime;
|
|
705
|
-
const rillError = mapProviderError(
|
|
706
|
-
"OpenAI",
|
|
707
|
-
error,
|
|
708
|
-
detectOpenAIError
|
|
709
|
-
);
|
|
710
|
-
emitExtensionEvent(ctx, {
|
|
711
|
-
event: "openai:error",
|
|
712
|
-
subsystem: "extension:openai",
|
|
713
|
-
error: rillError.message,
|
|
714
|
-
duration
|
|
715
|
-
});
|
|
716
|
-
throw rillError;
|
|
717
713
|
}
|
|
714
|
+
const resolve = async () => {
|
|
715
|
+
const startTime = Date.now();
|
|
716
|
+
try {
|
|
717
|
+
const response = await runner.finalChatCompletion();
|
|
718
|
+
const content = response.choices[0]?.message?.content ?? "";
|
|
719
|
+
const result = {
|
|
720
|
+
content,
|
|
721
|
+
model: response.model,
|
|
722
|
+
usage: {
|
|
723
|
+
input: response.usage?.prompt_tokens ?? 0,
|
|
724
|
+
output: response.usage?.completion_tokens ?? 0
|
|
725
|
+
},
|
|
726
|
+
stop_reason: response.choices[0]?.finish_reason ?? "unknown",
|
|
727
|
+
id: response.id,
|
|
728
|
+
messages: buildResponseMessages(
|
|
729
|
+
[
|
|
730
|
+
...system ? [{ role: "system", content: system }] : [],
|
|
731
|
+
{ role: "user", content: text }
|
|
732
|
+
],
|
|
733
|
+
content
|
|
734
|
+
)
|
|
735
|
+
};
|
|
736
|
+
const duration = Date.now() - startTime;
|
|
737
|
+
emitExtensionEvent(ctx, {
|
|
738
|
+
event: "openai:message",
|
|
739
|
+
subsystem: "extension:openai",
|
|
740
|
+
duration,
|
|
741
|
+
model: response.model,
|
|
742
|
+
usage: result.usage,
|
|
743
|
+
request: apiMessages,
|
|
744
|
+
content
|
|
745
|
+
});
|
|
746
|
+
return result;
|
|
747
|
+
} catch (error) {
|
|
748
|
+
const duration = Date.now() - startTime;
|
|
749
|
+
const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
|
|
750
|
+
emitExtensionEvent(ctx, {
|
|
751
|
+
event: "openai:error",
|
|
752
|
+
subsystem: "extension:openai",
|
|
753
|
+
error: rillError.message,
|
|
754
|
+
duration
|
|
755
|
+
});
|
|
756
|
+
throw rillError;
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
const retType = {
|
|
760
|
+
kind: "dict",
|
|
761
|
+
fields: {
|
|
762
|
+
content: { type: { kind: "string" } },
|
|
763
|
+
model: { type: { kind: "string" } },
|
|
764
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
765
|
+
stop_reason: { type: { kind: "string" } },
|
|
766
|
+
id: { type: { kind: "string" } },
|
|
767
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
return createRillStream({
|
|
771
|
+
chunks: chunks(),
|
|
772
|
+
resolve,
|
|
773
|
+
dispose: () => {
|
|
774
|
+
runner.abort();
|
|
775
|
+
},
|
|
776
|
+
chunkType: { kind: "string" },
|
|
777
|
+
retType
|
|
778
|
+
});
|
|
718
779
|
},
|
|
719
|
-
description: "Send single message to OpenAI API",
|
|
720
|
-
returnType: {
|
|
780
|
+
annotations: { description: "Send single message to OpenAI API" },
|
|
781
|
+
returnType: structureToTypeValue({
|
|
782
|
+
kind: "stream",
|
|
783
|
+
chunk: { kind: "string" },
|
|
784
|
+
ret: {
|
|
785
|
+
kind: "dict",
|
|
786
|
+
fields: {
|
|
787
|
+
content: { type: { kind: "string" } },
|
|
788
|
+
model: { type: { kind: "string" } },
|
|
789
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
790
|
+
stop_reason: { type: { kind: "string" } },
|
|
791
|
+
id: { type: { kind: "string" } },
|
|
792
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
})
|
|
721
796
|
},
|
|
722
797
|
// IR-5: openai::messages
|
|
723
798
|
messages: {
|
|
724
799
|
params: [
|
|
725
|
-
p.list("messages"),
|
|
726
|
-
p.dict("options", void 0, {}
|
|
800
|
+
p.list("messages", { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } }),
|
|
801
|
+
p.dict("options", void 0, {}, {
|
|
802
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
803
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 }
|
|
804
|
+
})
|
|
727
805
|
],
|
|
728
|
-
fn:
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
806
|
+
fn: (args, ctx) => {
|
|
807
|
+
const messages = args["messages"];
|
|
808
|
+
const options = args["options"] ?? {};
|
|
809
|
+
if (messages.length === 0) {
|
|
810
|
+
throw new RuntimeError6(
|
|
811
|
+
"RILL-R004",
|
|
812
|
+
"messages list cannot be empty"
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
816
|
+
const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
|
|
817
|
+
const apiMessages = [];
|
|
818
|
+
if (system !== void 0) {
|
|
819
|
+
apiMessages.push({
|
|
820
|
+
role: "system",
|
|
821
|
+
content: system
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
for (let i = 0; i < messages.length; i++) {
|
|
825
|
+
const msg = messages[i];
|
|
826
|
+
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
734
827
|
throw new RuntimeError6(
|
|
735
828
|
"RILL-R004",
|
|
736
|
-
"
|
|
829
|
+
"message missing required 'role' field"
|
|
737
830
|
);
|
|
738
831
|
}
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
if (system !== void 0) {
|
|
743
|
-
apiMessages.push({
|
|
744
|
-
role: "system",
|
|
745
|
-
content: system
|
|
746
|
-
});
|
|
832
|
+
const role = msg["role"];
|
|
833
|
+
if (role !== "user" && role !== "assistant" && role !== "tool") {
|
|
834
|
+
throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
|
|
747
835
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
836
|
+
if (role === "user" || role === "tool") {
|
|
837
|
+
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
751
838
|
throw new RuntimeError6(
|
|
752
839
|
"RILL-R004",
|
|
753
|
-
|
|
840
|
+
`${role} message requires 'content'`
|
|
754
841
|
);
|
|
755
842
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
843
|
+
apiMessages.push({
|
|
844
|
+
role,
|
|
845
|
+
content: msg["content"]
|
|
846
|
+
});
|
|
847
|
+
} else if (role === "assistant") {
|
|
848
|
+
const hasContent = "content" in msg && msg["content"];
|
|
849
|
+
const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
|
|
850
|
+
if (!hasContent && !hasToolCalls) {
|
|
851
|
+
throw new RuntimeError6(
|
|
852
|
+
"RILL-R004",
|
|
853
|
+
"assistant message requires 'content' or 'tool_calls'"
|
|
854
|
+
);
|
|
759
855
|
}
|
|
760
|
-
if (
|
|
761
|
-
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
762
|
-
throw new RuntimeError6(
|
|
763
|
-
"RILL-R004",
|
|
764
|
-
`${role} message requires 'content'`
|
|
765
|
-
);
|
|
766
|
-
}
|
|
856
|
+
if (hasContent) {
|
|
767
857
|
apiMessages.push({
|
|
768
|
-
role,
|
|
858
|
+
role: "assistant",
|
|
769
859
|
content: msg["content"]
|
|
770
860
|
});
|
|
771
|
-
} else if (role === "assistant") {
|
|
772
|
-
const hasContent = "content" in msg && msg["content"];
|
|
773
|
-
const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
|
|
774
|
-
if (!hasContent && !hasToolCalls) {
|
|
775
|
-
throw new RuntimeError6(
|
|
776
|
-
"RILL-R004",
|
|
777
|
-
"assistant message requires 'content' or 'tool_calls'"
|
|
778
|
-
);
|
|
779
|
-
}
|
|
780
|
-
if (hasContent) {
|
|
781
|
-
apiMessages.push({
|
|
782
|
-
role: "assistant",
|
|
783
|
-
content: msg["content"]
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
861
|
}
|
|
787
862
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
863
|
+
}
|
|
864
|
+
const runner = client.chat.completions.stream({
|
|
865
|
+
model: factoryModel,
|
|
866
|
+
max_completion_tokens: maxTokens,
|
|
867
|
+
messages: apiMessages,
|
|
868
|
+
stream_options: { include_usage: true },
|
|
869
|
+
...factoryTemperature !== void 0 ? { temperature: factoryTemperature } : {}
|
|
870
|
+
});
|
|
871
|
+
async function* chunks() {
|
|
872
|
+
try {
|
|
873
|
+
for await (const chunk of runner) {
|
|
874
|
+
const delta = chunk.choices[0]?.delta?.content;
|
|
875
|
+
if (delta) {
|
|
876
|
+
yield delta;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} catch (error) {
|
|
880
|
+
throw mapProviderError("OpenAI", error, detectOpenAIError);
|
|
795
881
|
}
|
|
796
|
-
const response = await client.chat.completions.create(apiParams);
|
|
797
|
-
const content = response.choices[0]?.message?.content ?? "";
|
|
798
|
-
const fullMessages = [
|
|
799
|
-
...messages.map((m) => {
|
|
800
|
-
const normalized = { role: m["role"] };
|
|
801
|
-
if ("content" in m) normalized["content"] = m["content"];
|
|
802
|
-
if ("tool_calls" in m) normalized["tool_calls"] = m["tool_calls"];
|
|
803
|
-
return normalized;
|
|
804
|
-
}),
|
|
805
|
-
{ role: "assistant", content }
|
|
806
|
-
];
|
|
807
|
-
const result2 = {
|
|
808
|
-
content,
|
|
809
|
-
model: response.model,
|
|
810
|
-
usage: {
|
|
811
|
-
input: response.usage?.prompt_tokens ?? 0,
|
|
812
|
-
output: response.usage?.completion_tokens ?? 0
|
|
813
|
-
},
|
|
814
|
-
stop_reason: response.choices[0]?.finish_reason ?? "unknown",
|
|
815
|
-
id: response.id,
|
|
816
|
-
messages: fullMessages
|
|
817
|
-
};
|
|
818
|
-
const duration = Date.now() - startTime;
|
|
819
|
-
emitExtensionEvent(ctx, {
|
|
820
|
-
event: "openai:messages",
|
|
821
|
-
subsystem: "extension:openai",
|
|
822
|
-
duration,
|
|
823
|
-
model: response.model,
|
|
824
|
-
usage: result2.usage,
|
|
825
|
-
request: apiMessages,
|
|
826
|
-
content
|
|
827
|
-
});
|
|
828
|
-
return result2;
|
|
829
|
-
} catch (error) {
|
|
830
|
-
const duration = Date.now() - startTime;
|
|
831
|
-
const rillError = mapProviderError(
|
|
832
|
-
"OpenAI",
|
|
833
|
-
error,
|
|
834
|
-
detectOpenAIError
|
|
835
|
-
);
|
|
836
|
-
emitExtensionEvent(ctx, {
|
|
837
|
-
event: "openai:error",
|
|
838
|
-
subsystem: "extension:openai",
|
|
839
|
-
error: rillError.message,
|
|
840
|
-
duration
|
|
841
|
-
});
|
|
842
|
-
throw rillError;
|
|
843
882
|
}
|
|
883
|
+
const resolve = async () => {
|
|
884
|
+
const startTime = Date.now();
|
|
885
|
+
try {
|
|
886
|
+
const response = await runner.finalChatCompletion();
|
|
887
|
+
const content = response.choices[0]?.message?.content ?? "";
|
|
888
|
+
const result = {
|
|
889
|
+
content,
|
|
890
|
+
model: response.model,
|
|
891
|
+
usage: {
|
|
892
|
+
input: response.usage?.prompt_tokens ?? 0,
|
|
893
|
+
output: response.usage?.completion_tokens ?? 0
|
|
894
|
+
},
|
|
895
|
+
stop_reason: response.choices[0]?.finish_reason ?? "unknown",
|
|
896
|
+
id: response.id,
|
|
897
|
+
messages: buildResponseMessages(
|
|
898
|
+
messages.map((m) => ({
|
|
899
|
+
role: m["role"],
|
|
900
|
+
content: m["content"] ?? ""
|
|
901
|
+
})),
|
|
902
|
+
content
|
|
903
|
+
)
|
|
904
|
+
};
|
|
905
|
+
const duration = Date.now() - startTime;
|
|
906
|
+
emitExtensionEvent(ctx, {
|
|
907
|
+
event: "openai:messages",
|
|
908
|
+
subsystem: "extension:openai",
|
|
909
|
+
duration,
|
|
910
|
+
model: response.model,
|
|
911
|
+
usage: result.usage,
|
|
912
|
+
request: apiMessages,
|
|
913
|
+
content
|
|
914
|
+
});
|
|
915
|
+
return result;
|
|
916
|
+
} catch (error) {
|
|
917
|
+
const duration = Date.now() - startTime;
|
|
918
|
+
const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
|
|
919
|
+
emitExtensionEvent(ctx, {
|
|
920
|
+
event: "openai:error",
|
|
921
|
+
subsystem: "extension:openai",
|
|
922
|
+
error: rillError.message,
|
|
923
|
+
duration
|
|
924
|
+
});
|
|
925
|
+
throw rillError;
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const retType = {
|
|
929
|
+
kind: "dict",
|
|
930
|
+
fields: {
|
|
931
|
+
content: { type: { kind: "string" } },
|
|
932
|
+
model: { type: { kind: "string" } },
|
|
933
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
934
|
+
stop_reason: { type: { kind: "string" } },
|
|
935
|
+
id: { type: { kind: "string" } },
|
|
936
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
return createRillStream({
|
|
940
|
+
chunks: chunks(),
|
|
941
|
+
resolve,
|
|
942
|
+
dispose: () => {
|
|
943
|
+
runner.abort();
|
|
944
|
+
},
|
|
945
|
+
chunkType: { kind: "string" },
|
|
946
|
+
retType
|
|
947
|
+
});
|
|
844
948
|
},
|
|
845
|
-
description: "Send multi-turn conversation to OpenAI API",
|
|
846
|
-
returnType: {
|
|
949
|
+
annotations: { description: "Send multi-turn conversation to OpenAI API" },
|
|
950
|
+
returnType: structureToTypeValue({
|
|
951
|
+
kind: "stream",
|
|
952
|
+
chunk: { kind: "string" },
|
|
953
|
+
ret: {
|
|
954
|
+
kind: "dict",
|
|
955
|
+
fields: {
|
|
956
|
+
content: { type: { kind: "string" } },
|
|
957
|
+
model: { type: { kind: "string" } },
|
|
958
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
959
|
+
stop_reason: { type: { kind: "string" } },
|
|
960
|
+
id: { type: { kind: "string" } },
|
|
961
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
})
|
|
847
965
|
},
|
|
848
966
|
// IR-6: openai::embed
|
|
849
967
|
embed: {
|
|
@@ -851,7 +969,7 @@ function createOpenAIExtension(config) {
|
|
|
851
969
|
fn: async (args, ctx) => {
|
|
852
970
|
const startTime = Date.now();
|
|
853
971
|
try {
|
|
854
|
-
const text = args[
|
|
972
|
+
const text = args["text"];
|
|
855
973
|
validateEmbedText(text.trim());
|
|
856
974
|
validateEmbedModel(factoryEmbedModel);
|
|
857
975
|
const response = await client.embeddings.create({
|
|
@@ -893,8 +1011,8 @@ function createOpenAIExtension(config) {
|
|
|
893
1011
|
throw rillError;
|
|
894
1012
|
}
|
|
895
1013
|
},
|
|
896
|
-
description: "Generate embedding vector for text",
|
|
897
|
-
returnType: {
|
|
1014
|
+
annotations: { description: "Generate embedding vector for text" },
|
|
1015
|
+
returnType: structureToTypeValue({ kind: "vector" })
|
|
898
1016
|
},
|
|
899
1017
|
// IR-7: openai::embed_batch
|
|
900
1018
|
embed_batch: {
|
|
@@ -902,7 +1020,7 @@ function createOpenAIExtension(config) {
|
|
|
902
1020
|
fn: async (args, ctx) => {
|
|
903
1021
|
const startTime = Date.now();
|
|
904
1022
|
try {
|
|
905
|
-
const texts = args[
|
|
1023
|
+
const texts = args["texts"];
|
|
906
1024
|
if (texts.length === 0) {
|
|
907
1025
|
return [];
|
|
908
1026
|
}
|
|
@@ -954,261 +1072,333 @@ function createOpenAIExtension(config) {
|
|
|
954
1072
|
throw rillError;
|
|
955
1073
|
}
|
|
956
1074
|
},
|
|
957
|
-
description: "Generate embedding vectors for multiple texts",
|
|
958
|
-
returnType: {
|
|
1075
|
+
annotations: { description: "Generate embedding vectors for multiple texts" },
|
|
1076
|
+
returnType: structureToTypeValue({ kind: "list", element: { kind: "vector" } })
|
|
959
1077
|
},
|
|
960
1078
|
// IR-8: openai::tool_loop
|
|
961
1079
|
tool_loop: {
|
|
962
1080
|
params: [
|
|
963
1081
|
p.str("prompt"),
|
|
964
|
-
|
|
1082
|
+
{
|
|
1083
|
+
name: "tools",
|
|
1084
|
+
type: { kind: "dict", valueType: { kind: "closure" } },
|
|
1085
|
+
defaultValue: void 0,
|
|
1086
|
+
annotations: {}
|
|
1087
|
+
},
|
|
1088
|
+
p.dict("options", void 0, void 0, {
|
|
1089
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
1090
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 },
|
|
1091
|
+
max_errors: { type: { kind: "number" }, defaultValue: 3 },
|
|
1092
|
+
max_turns: { type: { kind: "number" }, defaultValue: 10 },
|
|
1093
|
+
messages: { type: { kind: "list", element: { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } } }, defaultValue: [] }
|
|
1094
|
+
})
|
|
965
1095
|
],
|
|
966
|
-
fn:
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1096
|
+
fn: (args, ctx) => {
|
|
1097
|
+
const prompt = args["prompt"];
|
|
1098
|
+
const toolsDict = args["tools"];
|
|
1099
|
+
const options = args["options"] ?? {};
|
|
1100
|
+
if (prompt.trim().length === 0) {
|
|
1101
|
+
throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
|
|
1102
|
+
}
|
|
1103
|
+
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
1104
|
+
const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
|
|
1105
|
+
const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
|
|
1106
|
+
const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
|
|
1107
|
+
const messages = [];
|
|
1108
|
+
if (system !== void 0) {
|
|
1109
|
+
messages.push({
|
|
1110
|
+
role: "system",
|
|
1111
|
+
content: system
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
if ("messages" in options && Array.isArray(options["messages"])) {
|
|
1115
|
+
const prependedMessages = options["messages"];
|
|
1116
|
+
for (const msg of prependedMessages) {
|
|
1117
|
+
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
1118
|
+
throw new RuntimeError6(
|
|
1119
|
+
"RILL-R004",
|
|
1120
|
+
"message missing required 'role' field"
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
const role = msg["role"];
|
|
1124
|
+
if (role !== "user" && role !== "assistant") {
|
|
1125
|
+
throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
|
|
1126
|
+
}
|
|
1127
|
+
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
1128
|
+
throw new RuntimeError6(
|
|
1129
|
+
"RILL-R004",
|
|
1130
|
+
`${role} message requires 'content'`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
986
1133
|
messages.push({
|
|
987
|
-
role
|
|
988
|
-
content:
|
|
1134
|
+
role,
|
|
1135
|
+
content: msg["content"]
|
|
989
1136
|
});
|
|
990
1137
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1138
|
+
}
|
|
1139
|
+
messages.push({
|
|
1140
|
+
role: "user",
|
|
1141
|
+
content: prompt
|
|
1142
|
+
});
|
|
1143
|
+
const callbacks = {
|
|
1144
|
+
// Build OpenAI Tool format from tool definitions
|
|
1145
|
+
buildTools: (toolDefs) => {
|
|
1146
|
+
return toolDefs.map((def) => ({
|
|
1147
|
+
type: "function",
|
|
1148
|
+
function: {
|
|
1149
|
+
name: def.name,
|
|
1150
|
+
description: def.description,
|
|
1151
|
+
parameters: def.input_schema
|
|
999
1152
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1153
|
+
}));
|
|
1154
|
+
},
|
|
1155
|
+
// Call OpenAI API (non-streaming fallback)
|
|
1156
|
+
callAPI: async (msgs, tools, signal) => {
|
|
1157
|
+
const apiParams = {
|
|
1158
|
+
model: factoryModel,
|
|
1159
|
+
max_completion_tokens: maxTokens,
|
|
1160
|
+
messages: msgs,
|
|
1161
|
+
tools,
|
|
1162
|
+
tool_choice: "auto"
|
|
1163
|
+
};
|
|
1164
|
+
if (factoryTemperature !== void 0) {
|
|
1165
|
+
apiParams.temperature = factoryTemperature;
|
|
1166
|
+
}
|
|
1167
|
+
const response = await client.chat.completions.create(apiParams, { signal });
|
|
1168
|
+
return {
|
|
1169
|
+
...response,
|
|
1170
|
+
usage: {
|
|
1171
|
+
input_tokens: response.usage?.prompt_tokens ?? 0,
|
|
1172
|
+
output_tokens: response.usage?.completion_tokens ?? 0
|
|
1003
1173
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1174
|
+
};
|
|
1175
|
+
},
|
|
1176
|
+
// Call OpenAI API with streaming — IR-3: callAPIStreaming
|
|
1177
|
+
callAPIStreaming: async (msgs, tools, onTextDelta, signal) => {
|
|
1178
|
+
const streamRunner = client.chat.completions.stream({
|
|
1179
|
+
model: factoryModel,
|
|
1180
|
+
max_completion_tokens: maxTokens,
|
|
1181
|
+
messages: msgs,
|
|
1182
|
+
tools,
|
|
1183
|
+
tool_choice: "auto",
|
|
1184
|
+
stream_options: { include_usage: true },
|
|
1185
|
+
...factoryTemperature !== void 0 ? { temperature: factoryTemperature } : {}
|
|
1186
|
+
}, { signal });
|
|
1187
|
+
streamRunner.on("content", (delta) => {
|
|
1188
|
+
onTextDelta(delta);
|
|
1189
|
+
});
|
|
1190
|
+
const response = await streamRunner.finalChatCompletion();
|
|
1191
|
+
return {
|
|
1192
|
+
...response,
|
|
1193
|
+
usage: {
|
|
1194
|
+
input_tokens: response.usage?.prompt_tokens ?? 0,
|
|
1195
|
+
output_tokens: response.usage?.completion_tokens ?? 0
|
|
1009
1196
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1197
|
+
};
|
|
1198
|
+
},
|
|
1199
|
+
// Extract tool calls from OpenAI response
|
|
1200
|
+
extractToolCalls: (response) => {
|
|
1201
|
+
if (!response || typeof response !== "object" || !("choices" in response)) {
|
|
1202
|
+
return null;
|
|
1014
1203
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
apiParams.temperature = factoryTemperature;
|
|
1204
|
+
const choices = response.choices;
|
|
1205
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
const choice = choices[0];
|
|
1209
|
+
if (!choice || typeof choice !== "object" || !("message" in choice)) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
const message = choice.message;
|
|
1213
|
+
if (!message || typeof message !== "object" || !("tool_calls" in message)) {
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
const toolCalls = message.tool_calls;
|
|
1217
|
+
if (!toolCalls || !Array.isArray(toolCalls)) {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
const functionToolCalls = toolCalls.filter(
|
|
1221
|
+
(tc) => typeof tc === "object" && tc !== null && "type" in tc && tc.type === "function"
|
|
1222
|
+
);
|
|
1223
|
+
return functionToolCalls.map((tc) => {
|
|
1224
|
+
const functionCall = tc;
|
|
1225
|
+
const args2 = functionCall.function.arguments;
|
|
1226
|
+
let parsedArgs;
|
|
1227
|
+
try {
|
|
1228
|
+
parsedArgs = JSON.parse(args2);
|
|
1229
|
+
} catch {
|
|
1230
|
+
parsedArgs = {};
|
|
1043
1231
|
}
|
|
1044
|
-
const response2 = await client.chat.completions.create(apiParams);
|
|
1045
1232
|
return {
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
output_tokens: response2.usage?.completion_tokens ?? 0
|
|
1050
|
-
}
|
|
1233
|
+
id: tc.id,
|
|
1234
|
+
name: functionCall.function.name,
|
|
1235
|
+
input: parsedArgs
|
|
1051
1236
|
};
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
const choices = response2.choices;
|
|
1059
|
-
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1060
|
-
return null;
|
|
1061
|
-
}
|
|
1062
|
-
const choice = choices[0];
|
|
1063
|
-
if (!choice || typeof choice !== "object" || !("message" in choice)) {
|
|
1064
|
-
return null;
|
|
1065
|
-
}
|
|
1066
|
-
const message = choice.message;
|
|
1067
|
-
if (!message || typeof message !== "object" || !("tool_calls" in message)) {
|
|
1068
|
-
return null;
|
|
1069
|
-
}
|
|
1070
|
-
const toolCalls = message.tool_calls;
|
|
1071
|
-
if (!toolCalls || !Array.isArray(toolCalls)) {
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
const functionToolCalls = toolCalls.filter(
|
|
1075
|
-
(tc) => typeof tc === "object" && tc !== null && "type" in tc && tc.type === "function"
|
|
1076
|
-
);
|
|
1077
|
-
return functionToolCalls.map((tc) => {
|
|
1078
|
-
const functionCall = tc;
|
|
1079
|
-
const args2 = functionCall.function.arguments;
|
|
1080
|
-
let parsedArgs;
|
|
1081
|
-
try {
|
|
1082
|
-
parsedArgs = JSON.parse(args2);
|
|
1083
|
-
} catch {
|
|
1084
|
-
parsedArgs = {};
|
|
1085
|
-
}
|
|
1086
|
-
return {
|
|
1087
|
-
id: tc.id,
|
|
1088
|
-
name: functionCall.function.name,
|
|
1089
|
-
input: parsedArgs
|
|
1090
|
-
};
|
|
1091
|
-
});
|
|
1092
|
-
},
|
|
1093
|
-
// Extract assistant message (with tool_calls) from OpenAI response
|
|
1094
|
-
formatAssistantMessage: (response2) => {
|
|
1095
|
-
if (!response2 || typeof response2 !== "object" || !("choices" in response2)) {
|
|
1096
|
-
return null;
|
|
1097
|
-
}
|
|
1098
|
-
const choices = response2.choices;
|
|
1099
|
-
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1100
|
-
return null;
|
|
1101
|
-
}
|
|
1102
|
-
const choice = choices[0];
|
|
1103
|
-
if (!choice || typeof choice !== "object" || !("message" in choice)) {
|
|
1104
|
-
return null;
|
|
1105
|
-
}
|
|
1106
|
-
return choice.message;
|
|
1107
|
-
},
|
|
1108
|
-
// Format tool results into OpenAI message format
|
|
1109
|
-
formatToolResult: (toolResults) => {
|
|
1110
|
-
return toolResults.map((tr) => ({
|
|
1111
|
-
role: "tool",
|
|
1112
|
-
tool_call_id: tr.id,
|
|
1113
|
-
content: tr.error ? JSON.stringify({ error: tr.error, code: "RILL-R001" }) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
|
|
1114
|
-
}));
|
|
1237
|
+
});
|
|
1238
|
+
},
|
|
1239
|
+
// Extract assistant message (with tool_calls) from OpenAI response
|
|
1240
|
+
formatAssistantMessage: (response) => {
|
|
1241
|
+
if (!response || typeof response !== "object" || !("choices" in response)) {
|
|
1242
|
+
return null;
|
|
1115
1243
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
options["tools"],
|
|
1120
|
-
maxErrors,
|
|
1121
|
-
callbacks,
|
|
1122
|
-
(event, data) => {
|
|
1123
|
-
const eventMap = {
|
|
1124
|
-
tool_call: "openai:tool_call",
|
|
1125
|
-
tool_result: "openai:tool_result"
|
|
1126
|
-
};
|
|
1127
|
-
emitExtensionEvent(ctx, {
|
|
1128
|
-
event: eventMap[event] || event,
|
|
1129
|
-
subsystem: "extension:openai",
|
|
1130
|
-
...data
|
|
1131
|
-
});
|
|
1132
|
-
},
|
|
1133
|
-
maxTurns,
|
|
1134
|
-
ctx
|
|
1135
|
-
);
|
|
1136
|
-
const response = loopResult.response;
|
|
1137
|
-
const content = response?.choices[0]?.message?.content ?? "";
|
|
1138
|
-
const stopReason = loopResult.turns >= maxTurns ? "max_turns" : response?.choices[0]?.finish_reason ?? "stop";
|
|
1139
|
-
const fullMessages = [];
|
|
1140
|
-
for (const msg of messages) {
|
|
1141
|
-
if ("role" in msg && msg.role !== "system") {
|
|
1142
|
-
const historyMsg = {
|
|
1143
|
-
role: msg.role
|
|
1144
|
-
};
|
|
1145
|
-
if ("content" in msg && msg.content) {
|
|
1146
|
-
historyMsg["content"] = msg.content;
|
|
1147
|
-
}
|
|
1148
|
-
if ("tool_calls" in msg && msg.tool_calls) {
|
|
1149
|
-
historyMsg["tool_calls"] = msg.tool_calls;
|
|
1150
|
-
}
|
|
1151
|
-
fullMessages.push(historyMsg);
|
|
1244
|
+
const choices = response.choices;
|
|
1245
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1246
|
+
return null;
|
|
1152
1247
|
}
|
|
1248
|
+
const choice = choices[0];
|
|
1249
|
+
if (!choice || typeof choice !== "object" || !("message" in choice)) {
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
return choice.message;
|
|
1253
|
+
},
|
|
1254
|
+
// Format tool results into OpenAI message format
|
|
1255
|
+
formatToolResult: (toolResults) => {
|
|
1256
|
+
return toolResults.map((tr) => ({
|
|
1257
|
+
role: "tool",
|
|
1258
|
+
tool_call_id: tr.id,
|
|
1259
|
+
content: tr.error ? JSON.stringify({ error: tr.error, code: "RILL-R001" }) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
|
|
1260
|
+
}));
|
|
1153
1261
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1262
|
+
};
|
|
1263
|
+
const chunkBuffer = [];
|
|
1264
|
+
const yieldChunk = (chunk) => {
|
|
1265
|
+
chunkBuffer.push(chunk);
|
|
1266
|
+
};
|
|
1267
|
+
const toolLoopAbortController = new AbortController();
|
|
1268
|
+
const loopPromise = executeToolLoop(
|
|
1269
|
+
messages,
|
|
1270
|
+
toolsDict,
|
|
1271
|
+
maxErrors,
|
|
1272
|
+
callbacks,
|
|
1273
|
+
(event, data) => {
|
|
1274
|
+
const eventMap = {
|
|
1275
|
+
tool_call: "openai:tool_call",
|
|
1276
|
+
tool_result: "openai:tool_result"
|
|
1277
|
+
};
|
|
1278
|
+
emitExtensionEvent(ctx, {
|
|
1279
|
+
event: eventMap[event] || event,
|
|
1280
|
+
subsystem: "extension:openai",
|
|
1281
|
+
...data
|
|
1158
1282
|
});
|
|
1283
|
+
},
|
|
1284
|
+
maxTurns,
|
|
1285
|
+
ctx,
|
|
1286
|
+
yieldChunk,
|
|
1287
|
+
toolLoopAbortController.signal
|
|
1288
|
+
);
|
|
1289
|
+
async function* chunks() {
|
|
1290
|
+
try {
|
|
1291
|
+
await loopPromise;
|
|
1292
|
+
for (const chunk of chunkBuffer) {
|
|
1293
|
+
yield chunk;
|
|
1294
|
+
}
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
|
|
1297
|
+
throw rillError;
|
|
1159
1298
|
}
|
|
1160
|
-
const result2 = {
|
|
1161
|
-
content,
|
|
1162
|
-
model: factoryModel,
|
|
1163
|
-
usage: {
|
|
1164
|
-
input: loopResult.totalTokens.input,
|
|
1165
|
-
output: loopResult.totalTokens.output
|
|
1166
|
-
},
|
|
1167
|
-
stop_reason: stopReason,
|
|
1168
|
-
turns: loopResult.turns,
|
|
1169
|
-
messages: fullMessages
|
|
1170
|
-
};
|
|
1171
|
-
const duration = Date.now() - startTime;
|
|
1172
|
-
emitExtensionEvent(ctx, {
|
|
1173
|
-
event: "openai:tool_loop",
|
|
1174
|
-
subsystem: "extension:openai",
|
|
1175
|
-
turns: loopResult.turns,
|
|
1176
|
-
total_duration: duration,
|
|
1177
|
-
usage: result2.usage,
|
|
1178
|
-
request: messages,
|
|
1179
|
-
content
|
|
1180
|
-
});
|
|
1181
|
-
return result2;
|
|
1182
|
-
} catch (error) {
|
|
1183
|
-
const duration = Date.now() - startTime;
|
|
1184
|
-
const rillError = mapProviderError(
|
|
1185
|
-
"OpenAI",
|
|
1186
|
-
error,
|
|
1187
|
-
detectOpenAIError
|
|
1188
|
-
);
|
|
1189
|
-
emitExtensionEvent(ctx, {
|
|
1190
|
-
event: "openai:error",
|
|
1191
|
-
subsystem: "extension:openai",
|
|
1192
|
-
error: rillError.message,
|
|
1193
|
-
duration
|
|
1194
|
-
});
|
|
1195
|
-
throw rillError;
|
|
1196
1299
|
}
|
|
1300
|
+
const resolve = async () => {
|
|
1301
|
+
const startTime = Date.now();
|
|
1302
|
+
try {
|
|
1303
|
+
const loopResult = await loopPromise;
|
|
1304
|
+
const response = loopResult.response;
|
|
1305
|
+
const content = response?.choices[0]?.message?.content ?? "";
|
|
1306
|
+
const stopReason = loopResult.turns >= maxTurns ? "max_turns" : response?.choices[0]?.finish_reason ?? "stop";
|
|
1307
|
+
const inputMessages = messages.filter((m) => "role" in m && m["role"] !== "system").map((m) => {
|
|
1308
|
+
const msg = m;
|
|
1309
|
+
return {
|
|
1310
|
+
role: msg["role"],
|
|
1311
|
+
content: msg["content"] == null ? "" : typeof msg["content"] === "string" ? msg["content"] : JSON.stringify(msg["content"])
|
|
1312
|
+
};
|
|
1313
|
+
});
|
|
1314
|
+
const result = {
|
|
1315
|
+
content,
|
|
1316
|
+
model: factoryModel,
|
|
1317
|
+
usage: {
|
|
1318
|
+
input: loopResult.totalTokens.input,
|
|
1319
|
+
output: loopResult.totalTokens.output
|
|
1320
|
+
},
|
|
1321
|
+
stop_reason: stopReason,
|
|
1322
|
+
turns: loopResult.turns,
|
|
1323
|
+
messages: response ? buildResponseMessages(inputMessages, content) : inputMessages
|
|
1324
|
+
};
|
|
1325
|
+
const duration = Date.now() - startTime;
|
|
1326
|
+
emitExtensionEvent(ctx, {
|
|
1327
|
+
event: "openai:tool_loop",
|
|
1328
|
+
subsystem: "extension:openai",
|
|
1329
|
+
turns: loopResult.turns,
|
|
1330
|
+
total_duration: duration,
|
|
1331
|
+
usage: result.usage,
|
|
1332
|
+
request: messages,
|
|
1333
|
+
content
|
|
1334
|
+
});
|
|
1335
|
+
return result;
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
const duration = Date.now() - startTime;
|
|
1338
|
+
const rillError = mapProviderError("OpenAI", error, detectOpenAIError);
|
|
1339
|
+
emitExtensionEvent(ctx, {
|
|
1340
|
+
event: "openai:error",
|
|
1341
|
+
subsystem: "extension:openai",
|
|
1342
|
+
error: rillError.message,
|
|
1343
|
+
duration
|
|
1344
|
+
});
|
|
1345
|
+
throw rillError;
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
const retType = {
|
|
1349
|
+
kind: "dict",
|
|
1350
|
+
fields: {
|
|
1351
|
+
content: { type: { kind: "string" } },
|
|
1352
|
+
model: { type: { kind: "string" } },
|
|
1353
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
1354
|
+
stop_reason: { type: { kind: "string" } },
|
|
1355
|
+
turns: { type: { kind: "number" } },
|
|
1356
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
return createRillStream({
|
|
1360
|
+
chunks: chunks(),
|
|
1361
|
+
resolve,
|
|
1362
|
+
dispose: () => {
|
|
1363
|
+
toolLoopAbortController.abort();
|
|
1364
|
+
},
|
|
1365
|
+
chunkType: { kind: "dict" },
|
|
1366
|
+
retType
|
|
1367
|
+
});
|
|
1197
1368
|
},
|
|
1198
|
-
description: "Execute tool-use loop with OpenAI API",
|
|
1199
|
-
returnType: {
|
|
1369
|
+
annotations: { description: "Execute tool-use loop with OpenAI API" },
|
|
1370
|
+
returnType: structureToTypeValue({
|
|
1371
|
+
kind: "stream",
|
|
1372
|
+
chunk: { kind: "dict" },
|
|
1373
|
+
ret: {
|
|
1374
|
+
kind: "dict",
|
|
1375
|
+
fields: {
|
|
1376
|
+
content: { type: { kind: "string" } },
|
|
1377
|
+
model: { type: { kind: "string" } },
|
|
1378
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
1379
|
+
stop_reason: { type: { kind: "string" } },
|
|
1380
|
+
turns: { type: { kind: "number" } },
|
|
1381
|
+
messages: { type: { kind: "list", element: { kind: "dict" } } }
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
})
|
|
1200
1385
|
},
|
|
1201
1386
|
// IR-3: openai::generate
|
|
1202
1387
|
generate: {
|
|
1203
1388
|
params: [
|
|
1204
1389
|
p.str("prompt"),
|
|
1205
|
-
p.dict("options"
|
|
1390
|
+
p.dict("options", void 0, {}, {
|
|
1391
|
+
schema: { type: { kind: "dict" } },
|
|
1392
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
1393
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 },
|
|
1394
|
+
messages: { type: { kind: "list", element: { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } } }, defaultValue: [] }
|
|
1395
|
+
})
|
|
1206
1396
|
],
|
|
1207
1397
|
fn: async (args, ctx) => {
|
|
1208
1398
|
const startTime = Date.now();
|
|
1209
1399
|
try {
|
|
1210
|
-
const prompt = args[
|
|
1211
|
-
const options = args[
|
|
1400
|
+
const prompt = args["prompt"];
|
|
1401
|
+
const options = args["options"] ?? {};
|
|
1212
1402
|
if (!("schema" in options) || options["schema"] === null || options["schema"] === void 0) {
|
|
1213
1403
|
throw new RuntimeError6(
|
|
1214
1404
|
"RILL-R004",
|
|
@@ -1218,7 +1408,7 @@ function createOpenAIExtension(config) {
|
|
|
1218
1408
|
const rillSchema = options["schema"];
|
|
1219
1409
|
const jsonSchema = buildJsonSchema(rillSchema);
|
|
1220
1410
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
1221
|
-
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
1411
|
+
const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
|
|
1222
1412
|
const apiMessages = [];
|
|
1223
1413
|
if (system !== void 0) {
|
|
1224
1414
|
apiMessages.push({
|
|
@@ -1280,7 +1470,7 @@ function createOpenAIExtension(config) {
|
|
|
1280
1470
|
`generate: failed to parse response JSON: ${detail}`
|
|
1281
1471
|
);
|
|
1282
1472
|
}
|
|
1283
|
-
const
|
|
1473
|
+
const result = {
|
|
1284
1474
|
data,
|
|
1285
1475
|
raw,
|
|
1286
1476
|
model: response.model,
|
|
@@ -1297,11 +1487,11 @@ function createOpenAIExtension(config) {
|
|
|
1297
1487
|
subsystem: "extension:openai",
|
|
1298
1488
|
duration,
|
|
1299
1489
|
model: response.model,
|
|
1300
|
-
usage:
|
|
1490
|
+
usage: result.usage,
|
|
1301
1491
|
request: apiMessages,
|
|
1302
1492
|
content: raw
|
|
1303
1493
|
});
|
|
1304
|
-
return
|
|
1494
|
+
return result;
|
|
1305
1495
|
} catch (error) {
|
|
1306
1496
|
const duration = Date.now() - startTime;
|
|
1307
1497
|
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("OpenAI", error, detectOpenAIError);
|
|
@@ -1314,16 +1504,35 @@ function createOpenAIExtension(config) {
|
|
|
1314
1504
|
throw rillError;
|
|
1315
1505
|
}
|
|
1316
1506
|
},
|
|
1317
|
-
description: "Generate structured output from OpenAI API",
|
|
1318
|
-
returnType: {
|
|
1507
|
+
annotations: { description: "Generate structured output from OpenAI API" },
|
|
1508
|
+
returnType: structureToTypeValue({
|
|
1509
|
+
kind: "dict",
|
|
1510
|
+
fields: {
|
|
1511
|
+
data: { type: { kind: "any" } },
|
|
1512
|
+
raw: { type: { kind: "string" } },
|
|
1513
|
+
model: { type: { kind: "string" } },
|
|
1514
|
+
usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
|
|
1515
|
+
stop_reason: { type: { kind: "string" } },
|
|
1516
|
+
id: { type: { kind: "string" } }
|
|
1517
|
+
}
|
|
1518
|
+
})
|
|
1319
1519
|
}
|
|
1320
1520
|
};
|
|
1321
|
-
|
|
1322
|
-
|
|
1521
|
+
const callableDict = {
|
|
1522
|
+
message: toCallable(fnDict.message),
|
|
1523
|
+
messages: toCallable(fnDict.messages),
|
|
1524
|
+
embed: toCallable(fnDict.embed),
|
|
1525
|
+
embed_batch: toCallable(fnDict.embed_batch),
|
|
1526
|
+
tool_loop: toCallable(fnDict.tool_loop),
|
|
1527
|
+
generate: toCallable(fnDict.generate)
|
|
1528
|
+
};
|
|
1529
|
+
return { value: callableDict, dispose };
|
|
1323
1530
|
}
|
|
1324
1531
|
|
|
1325
1532
|
// src/index.ts
|
|
1326
|
-
var
|
|
1533
|
+
var _require = createRequire(import.meta.url);
|
|
1534
|
+
var _pkg = _require("../package.json");
|
|
1535
|
+
var VERSION = _pkg.version;
|
|
1327
1536
|
var configSchema = {
|
|
1328
1537
|
api_key: { type: "string", required: true, secret: true },
|
|
1329
1538
|
model: { type: "string", required: true },
|
|
@@ -1335,8 +1544,14 @@ var configSchema = {
|
|
|
1335
1544
|
system: { type: "string" },
|
|
1336
1545
|
embed_model: { type: "string" }
|
|
1337
1546
|
};
|
|
1547
|
+
var extensionManifest = {
|
|
1548
|
+
factory: createOpenAIExtension,
|
|
1549
|
+
configSchema,
|
|
1550
|
+
version: VERSION
|
|
1551
|
+
};
|
|
1338
1552
|
export {
|
|
1339
1553
|
VERSION,
|
|
1340
1554
|
configSchema,
|
|
1341
|
-
createOpenAIExtension
|
|
1555
|
+
createOpenAIExtension,
|
|
1556
|
+
extensionManifest
|
|
1342
1557
|
};
|