@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/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
- isDict as isDict2,
8
- isVector
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.type === "closure" || rillType.type === "tuple") {
101
- throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.type}`);
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.type === "any") {
108
+ if (rillType.kind === "any") {
104
109
  return {};
105
110
  }
106
- if (rillType.type === "list") {
111
+ if (rillType.kind === "list") {
112
+ const listType = rillType;
107
113
  const property = { type: "array" };
108
- if (rillType.element !== void 0) {
109
- property.items = buildPropertyFromStructuralType(rillType.element);
114
+ if (listType.element !== void 0) {
115
+ property.items = buildPropertyFromStructuralType(listType.element);
110
116
  }
111
117
  return property;
112
118
  }
113
- if (rillType.type === "dict") {
119
+ if (rillType.kind === "dict") {
114
120
  return { type: "object" };
115
121
  }
116
- return { type: mapRillType(rillType.type) };
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.type === "closure") {
127
+ if (type.kind === "closure") {
122
128
  const closureParams = type.params ?? [];
123
129
  for (let i = 0; i < closureParams.length; i++) {
124
- const [paramName, paramType] = closureParams[i];
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
- let args;
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(args, ctx);
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
- let description;
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
- type: "closure",
349
- params: params.map((p2) => [p2.name, p2.type ?? { type: "any" }])
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
- response = await callbacks.callAPI(currentMessages, providerTools);
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: { type: "string" },
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: { type: "number" },
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: { type: "bool" },
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
- * @returns RillParam with type 'dict'
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: { type: "dict" },
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 ? { type: "list", element: itemType } : { type: "list" };
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: { type: "closure" },
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 result = {
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: async (args, ctx) => {
647
- const startTime = Date.now();
648
- try {
649
- const text = args[0];
650
- const options = args[1] ?? {};
651
- if (text.trim().length === 0) {
652
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
653
- }
654
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
655
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
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: "user",
665
- content: text
687
+ role: "system",
688
+ content: system
666
689
  });
667
- const apiParams = {
668
- model: factoryModel,
669
- max_completion_tokens: maxTokens,
670
- messages: apiMessages
671
- };
672
- if (factoryTemperature !== void 0) {
673
- apiParams.temperature = factoryTemperature;
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: { type: "dict" }
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: async (args, ctx) => {
729
- const startTime = Date.now();
730
- try {
731
- const messages = args[0];
732
- const options = args[1] ?? {};
733
- if (messages.length === 0) {
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
- "messages list cannot be empty"
829
+ "message missing required 'role' field"
737
830
  );
738
831
  }
739
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
740
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
741
- const apiMessages = [];
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
- for (let i = 0; i < messages.length; i++) {
749
- const msg = messages[i];
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
- "message missing required 'role' field"
840
+ `${role} message requires 'content'`
754
841
  );
755
842
  }
756
- const role = msg["role"];
757
- if (role !== "user" && role !== "assistant" && role !== "tool") {
758
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
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 (role === "user" || role === "tool") {
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
- const apiParams = {
789
- model: factoryModel,
790
- max_completion_tokens: maxTokens,
791
- messages: apiMessages
792
- };
793
- if (factoryTemperature !== void 0) {
794
- apiParams.temperature = factoryTemperature;
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: { type: "dict" }
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[0];
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: { type: "vector" }
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[0];
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: { type: "list" }
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
- p.dict("options", void 0, {})
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: async (args, ctx) => {
967
- const startTime = Date.now();
968
- try {
969
- const prompt = args[0];
970
- const options = args[1] ?? {};
971
- if (prompt.trim().length === 0) {
972
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
973
- }
974
- if (!("tools" in options) || !isDict2(options["tools"])) {
975
- throw new RuntimeError6(
976
- "RILL-R004",
977
- "tool_loop requires 'tools' option"
978
- );
979
- }
980
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
981
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
982
- const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
983
- const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
984
- const messages = [];
985
- if (system !== void 0) {
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: "system",
988
- content: system
1134
+ role,
1135
+ content: msg["content"]
989
1136
  });
990
1137
  }
991
- if ("messages" in options && Array.isArray(options["messages"])) {
992
- const prependedMessages = options["messages"];
993
- for (const msg of prependedMessages) {
994
- if (!msg || typeof msg !== "object" || !("role" in msg)) {
995
- throw new RuntimeError6(
996
- "RILL-R004",
997
- "message missing required 'role' field"
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
- const role = msg["role"];
1001
- if (role !== "user" && role !== "assistant") {
1002
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
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
- if (!("content" in msg) || typeof msg["content"] !== "string") {
1005
- throw new RuntimeError6(
1006
- "RILL-R004",
1007
- `${role} message requires 'content'`
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
- messages.push({
1011
- role,
1012
- content: msg["content"]
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
- messages.push({
1017
- role: "user",
1018
- content: prompt
1019
- });
1020
- const callbacks = {
1021
- // Build OpenAI Tool format from tool definitions
1022
- buildTools: (toolDefs) => {
1023
- return toolDefs.map((def) => ({
1024
- type: "function",
1025
- function: {
1026
- name: def.name,
1027
- description: def.description,
1028
- parameters: def.input_schema
1029
- }
1030
- }));
1031
- },
1032
- // Call OpenAI API
1033
- callAPI: async (msgs, tools) => {
1034
- const apiParams = {
1035
- model: factoryModel,
1036
- max_completion_tokens: maxTokens,
1037
- messages: msgs,
1038
- tools,
1039
- tool_choice: "auto"
1040
- };
1041
- if (factoryTemperature !== void 0) {
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
- ...response2,
1047
- usage: {
1048
- input_tokens: response2.usage?.prompt_tokens ?? 0,
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
- // Extract tool calls from OpenAI response
1054
- extractToolCalls: (response2) => {
1055
- if (!response2 || typeof response2 !== "object" || !("choices" in response2)) {
1056
- return null;
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
- const loopResult = await executeToolLoop(
1118
- messages,
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
- if (response) {
1155
- fullMessages.push({
1156
- role: "assistant",
1157
- content
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: { type: "dict" }
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[0];
1211
- const options = args[1] ?? {};
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 result2 = {
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: result2.usage,
1490
+ usage: result.usage,
1301
1491
  request: apiMessages,
1302
1492
  content: raw
1303
1493
  });
1304
- return result2;
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: { type: "dict" }
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
- result.dispose = dispose;
1322
- return result;
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 VERSION = "0.0.1";
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
  };