@rcrsr/rill-ext-openai 0.16.0 → 0.18.1

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
@@ -5,11 +5,12 @@ import { createRequire } from "module";
5
5
  import OpenAI from "openai";
6
6
  import {
7
7
  RuntimeError as RuntimeError6,
8
+ createRillStream,
8
9
  emitExtensionEvent,
9
10
  createVector,
10
- isDict as isDict2,
11
11
  isVector,
12
- rillTypeToTypeValue
12
+ structureToTypeValue,
13
+ toCallable
13
14
  } from "@rcrsr/rill";
14
15
 
15
16
  // ../../shared/ext-llm/dist/validation.js
@@ -49,7 +50,7 @@ function validateEmbedBatch(texts) {
49
50
  if (typeof item !== "string") {
50
51
  throw new RuntimeError("RILL-R001", "embed_batch requires list of strings");
51
52
  }
52
- if (item === "") {
53
+ if (item.trim() === "") {
53
54
  throw new RuntimeError("RILL-R001", `embed text cannot be empty at index ${i}`);
54
55
  }
55
56
  validated.push(item);
@@ -101,28 +102,29 @@ function mapRillType(rillType) {
101
102
  return jsonType;
102
103
  }
103
104
  function buildPropertyFromStructuralType(rillType) {
104
- if (rillType.type === "closure" || rillType.type === "tuple") {
105
- 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}`);
106
107
  }
107
- if (rillType.type === "any") {
108
+ if (rillType.kind === "any") {
108
109
  return {};
109
110
  }
110
- if (rillType.type === "list") {
111
+ if (rillType.kind === "list") {
112
+ const listType = rillType;
111
113
  const property = { type: "array" };
112
- if (rillType.element !== void 0) {
113
- property.items = buildPropertyFromStructuralType(rillType.element);
114
+ if (listType.element !== void 0) {
115
+ property.items = buildPropertyFromStructuralType(listType.element);
114
116
  }
115
117
  return property;
116
118
  }
117
- if (rillType.type === "dict") {
119
+ if (rillType.kind === "dict") {
118
120
  return { type: "object" };
119
121
  }
120
- return { type: mapRillType(rillType.type) };
122
+ return { type: mapRillType(rillType.kind) };
121
123
  }
122
124
  function buildJsonSchemaFromStructuralType(type, params) {
123
125
  const properties = {};
124
126
  const required = [];
125
- if (type.type === "closure") {
127
+ if (type.kind === "closure") {
126
128
  const closureParams = type.params ?? [];
127
129
  for (let i = 0; i < closureParams.length; i++) {
128
130
  const fieldDef = closureParams[i];
@@ -238,7 +240,7 @@ async function executeToolCall(toolName, toolInput, tools, context) {
238
240
  return value !== void 0 ? value : void 0;
239
241
  });
240
242
  } else {
241
- args = [inputDict];
243
+ args = [];
242
244
  }
243
245
  return await invokeCallable(callable, args, context);
244
246
  }
@@ -323,7 +325,7 @@ function patchResponseToolCallNames(response, nameMap) {
323
325
  }
324
326
  }
325
327
  }
326
- 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) {
327
329
  if (tools === void 0) {
328
330
  throw new RuntimeError4("RILL-R004", "tools parameter is required");
329
331
  }
@@ -345,8 +347,8 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
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) => ({ name: p2.name, type: 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
  };
@@ -550,7 +575,7 @@ var p = {
550
575
  */
551
576
  dict(name, desc, def, fields) {
552
577
  validateParamName(name);
553
- const type = fields !== void 0 ? { type: "dict", fields } : { type: "dict" };
578
+ const type = fields !== void 0 ? { kind: "dict", fields } : { kind: "dict" };
554
579
  return {
555
580
  name,
556
581
  type,
@@ -568,7 +593,7 @@ var p = {
568
593
  */
569
594
  list(name, itemType, desc) {
570
595
  validateParamName(name);
571
- const type = itemType !== void 0 ? { type: "list", element: itemType } : { type: "list" };
596
+ const type = itemType !== void 0 ? { kind: "list", element: itemType } : { kind: "list" };
572
597
  return {
573
598
  name,
574
599
  type,
@@ -587,7 +612,7 @@ var p = {
587
612
  validateParamName(name);
588
613
  return {
589
614
  name,
590
- type: { type: "closure" },
615
+ type: { kind: "closure" },
591
616
  defaultValue: void 0,
592
617
  annotations: buildAnnotations(desc)
593
618
  };
@@ -638,238 +663,303 @@ function createOpenAIExtension(config) {
638
663
  console.warn(`Failed to cleanup OpenAI SDK: ${message}`);
639
664
  }
640
665
  };
641
- const result = {
666
+ const fnDict = {
642
667
  // IR-4: openai::message
643
668
  message: {
644
669
  params: [
645
670
  p.str("text"),
646
671
  p.dict("options", void 0, {}, {
647
- system: { type: { type: "string" }, defaultValue: "" },
648
- max_tokens: { type: { type: "number" }, defaultValue: 0 }
672
+ system: { type: { kind: "string" }, defaultValue: "" },
673
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 }
649
674
  })
650
675
  ],
651
- fn: async (args, ctx) => {
652
- const startTime = Date.now();
653
- try {
654
- const text = args["text"];
655
- const options = args["options"] ?? {};
656
- if (text.trim().length === 0) {
657
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
658
- }
659
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
660
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
661
- const apiMessages = [];
662
- if (system !== void 0) {
663
- apiMessages.push({
664
- role: "system",
665
- content: system
666
- });
667
- }
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) {
668
686
  apiMessages.push({
669
- role: "user",
670
- content: text
687
+ role: "system",
688
+ content: system
671
689
  });
672
- const apiParams = {
673
- model: factoryModel,
674
- max_completion_tokens: maxTokens,
675
- messages: apiMessages
676
- };
677
- if (factoryTemperature !== void 0) {
678
- 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);
679
712
  }
680
- const response = await client.chat.completions.create(apiParams);
681
- const content = response.choices[0]?.message?.content ?? "";
682
- const result2 = {
683
- content,
684
- model: response.model,
685
- usage: {
686
- input: response.usage?.prompt_tokens ?? 0,
687
- output: response.usage?.completion_tokens ?? 0
688
- },
689
- stop_reason: response.choices[0]?.finish_reason ?? "unknown",
690
- id: response.id,
691
- messages: [
692
- ...system ? [{ role: "system", content: system }] : [],
693
- { role: "user", content: text },
694
- { role: "assistant", content }
695
- ]
696
- };
697
- const duration = Date.now() - startTime;
698
- emitExtensionEvent(ctx, {
699
- event: "openai:message",
700
- subsystem: "extension:openai",
701
- duration,
702
- model: response.model,
703
- usage: result2.usage,
704
- request: apiMessages,
705
- content
706
- });
707
- return result2;
708
- } catch (error) {
709
- const duration = Date.now() - startTime;
710
- const rillError = mapProviderError(
711
- "OpenAI",
712
- error,
713
- detectOpenAIError
714
- );
715
- emitExtensionEvent(ctx, {
716
- event: "openai:error",
717
- subsystem: "extension:openai",
718
- error: rillError.message,
719
- duration
720
- });
721
- throw rillError;
722
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
+ });
723
779
  },
724
780
  annotations: { description: "Send single message to OpenAI API" },
725
- returnType: rillTypeToTypeValue({
726
- type: "dict",
727
- fields: {
728
- content: { type: { type: "string" } },
729
- model: { type: { type: "string" } },
730
- usage: { type: { type: "dict", fields: { input: { type: { type: "number" } }, output: { type: { type: "number" } } } } },
731
- stop_reason: { type: { type: "string" } },
732
- id: { type: { type: "string" } },
733
- messages: { type: { type: "list", element: { type: "dict" } } }
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
+ }
734
794
  }
735
795
  })
736
796
  },
737
797
  // IR-5: openai::messages
738
798
  messages: {
739
799
  params: [
740
- p.list("messages", { type: "dict", fields: { role: { type: { type: "string" } }, content: { type: { type: "string" } } } }),
800
+ p.list("messages", { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } }),
741
801
  p.dict("options", void 0, {}, {
742
- system: { type: { type: "string" }, defaultValue: "" },
743
- max_tokens: { type: { type: "number" }, defaultValue: 0 }
802
+ system: { type: { kind: "string" }, defaultValue: "" },
803
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 }
744
804
  })
745
805
  ],
746
- fn: async (args, ctx) => {
747
- const startTime = Date.now();
748
- try {
749
- const messages = args["messages"];
750
- const options = args["options"] ?? {};
751
- 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)) {
752
827
  throw new RuntimeError6(
753
828
  "RILL-R004",
754
- "messages list cannot be empty"
829
+ "message missing required 'role' field"
755
830
  );
756
831
  }
757
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
758
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
759
- const apiMessages = [];
760
- if (system !== void 0) {
761
- apiMessages.push({
762
- role: "system",
763
- content: system
764
- });
832
+ const role = msg["role"];
833
+ if (role !== "user" && role !== "assistant" && role !== "tool") {
834
+ throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
765
835
  }
766
- for (let i = 0; i < messages.length; i++) {
767
- const msg = messages[i];
768
- if (!msg || typeof msg !== "object" || !("role" in msg)) {
836
+ if (role === "user" || role === "tool") {
837
+ if (!("content" in msg) || typeof msg["content"] !== "string") {
769
838
  throw new RuntimeError6(
770
839
  "RILL-R004",
771
- "message missing required 'role' field"
840
+ `${role} message requires 'content'`
772
841
  );
773
842
  }
774
- const role = msg["role"];
775
- if (role !== "user" && role !== "assistant" && role !== "tool") {
776
- 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
+ );
777
855
  }
778
- if (role === "user" || role === "tool") {
779
- if (!("content" in msg) || typeof msg["content"] !== "string") {
780
- throw new RuntimeError6(
781
- "RILL-R004",
782
- `${role} message requires 'content'`
783
- );
784
- }
856
+ if (hasContent) {
785
857
  apiMessages.push({
786
- role,
858
+ role: "assistant",
787
859
  content: msg["content"]
788
860
  });
789
- } else if (role === "assistant") {
790
- const hasContent = "content" in msg && msg["content"];
791
- const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
792
- if (!hasContent && !hasToolCalls) {
793
- throw new RuntimeError6(
794
- "RILL-R004",
795
- "assistant message requires 'content' or 'tool_calls'"
796
- );
797
- }
798
- if (hasContent) {
799
- apiMessages.push({
800
- role: "assistant",
801
- content: msg["content"]
802
- });
803
- }
804
861
  }
805
862
  }
806
- const apiParams = {
807
- model: factoryModel,
808
- max_completion_tokens: maxTokens,
809
- messages: apiMessages
810
- };
811
- if (factoryTemperature !== void 0) {
812
- 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);
813
881
  }
814
- const response = await client.chat.completions.create(apiParams);
815
- const content = response.choices[0]?.message?.content ?? "";
816
- const fullMessages = [
817
- ...messages.map((m) => {
818
- const normalized = { role: m["role"] };
819
- if ("content" in m) normalized["content"] = m["content"];
820
- if ("tool_calls" in m) normalized["tool_calls"] = m["tool_calls"];
821
- return normalized;
822
- }),
823
- { role: "assistant", content }
824
- ];
825
- const result2 = {
826
- content,
827
- model: response.model,
828
- usage: {
829
- input: response.usage?.prompt_tokens ?? 0,
830
- output: response.usage?.completion_tokens ?? 0
831
- },
832
- stop_reason: response.choices[0]?.finish_reason ?? "unknown",
833
- id: response.id,
834
- messages: fullMessages
835
- };
836
- const duration = Date.now() - startTime;
837
- emitExtensionEvent(ctx, {
838
- event: "openai:messages",
839
- subsystem: "extension:openai",
840
- duration,
841
- model: response.model,
842
- usage: result2.usage,
843
- request: apiMessages,
844
- content
845
- });
846
- return result2;
847
- } catch (error) {
848
- const duration = Date.now() - startTime;
849
- const rillError = mapProviderError(
850
- "OpenAI",
851
- error,
852
- detectOpenAIError
853
- );
854
- emitExtensionEvent(ctx, {
855
- event: "openai:error",
856
- subsystem: "extension:openai",
857
- error: rillError.message,
858
- duration
859
- });
860
- throw rillError;
861
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
+ });
862
948
  },
863
949
  annotations: { description: "Send multi-turn conversation to OpenAI API" },
864
- returnType: rillTypeToTypeValue({
865
- type: "dict",
866
- fields: {
867
- content: { type: { type: "string" } },
868
- model: { type: { type: "string" } },
869
- usage: { type: { type: "dict", fields: { input: { type: { type: "number" } }, output: { type: { type: "number" } } } } },
870
- stop_reason: { type: { type: "string" } },
871
- id: { type: { type: "string" } },
872
- messages: { type: { type: "list", element: { type: "dict" } } }
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
+ }
873
963
  }
874
964
  })
875
965
  },
@@ -922,7 +1012,7 @@ function createOpenAIExtension(config) {
922
1012
  }
923
1013
  },
924
1014
  annotations: { description: "Generate embedding vector for text" },
925
- returnType: rillTypeToTypeValue({ type: "vector" })
1015
+ returnType: structureToTypeValue({ kind: "vector" })
926
1016
  },
927
1017
  // IR-7: openai::embed_batch
928
1018
  embed_batch: {
@@ -983,263 +1073,313 @@ function createOpenAIExtension(config) {
983
1073
  }
984
1074
  },
985
1075
  annotations: { description: "Generate embedding vectors for multiple texts" },
986
- returnType: rillTypeToTypeValue({ type: "list", element: { type: "vector" } })
1076
+ returnType: structureToTypeValue({ kind: "list", element: { kind: "vector" } })
987
1077
  },
988
1078
  // IR-8: openai::tool_loop
989
1079
  tool_loop: {
990
1080
  params: [
991
1081
  p.str("prompt"),
992
- p.dict("options", void 0, {}, {
993
- tools: { type: { type: "dict" } },
994
- system: { type: { type: "string" }, defaultValue: "" },
995
- max_tokens: { type: { type: "number" }, defaultValue: 0 },
996
- max_errors: { type: { type: "number" }, defaultValue: 3 },
997
- max_turns: { type: { type: "number" }, defaultValue: 10 },
998
- messages: { type: { type: "list", element: { type: "dict", fields: { role: { type: { type: "string" } }, content: { type: { type: "string" } } } } }, defaultValue: [] }
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: [] }
999
1094
  })
1000
1095
  ],
1001
- fn: async (args, ctx) => {
1002
- const startTime = Date.now();
1003
- try {
1004
- const prompt = args["prompt"];
1005
- const options = args["options"] ?? {};
1006
- if (prompt.trim().length === 0) {
1007
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
1008
- }
1009
- if (!("tools" in options) || !isDict2(options["tools"])) {
1010
- throw new RuntimeError6(
1011
- "RILL-R004",
1012
- "tool_loop requires 'tools' option"
1013
- );
1014
- }
1015
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1016
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
1017
- const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
1018
- const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
1019
- const messages = [];
1020
- 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
+ }
1021
1133
  messages.push({
1022
- role: "system",
1023
- content: system
1134
+ role,
1135
+ content: msg["content"]
1024
1136
  });
1025
1137
  }
1026
- if ("messages" in options && Array.isArray(options["messages"])) {
1027
- const prependedMessages = options["messages"];
1028
- for (const msg of prependedMessages) {
1029
- if (!msg || typeof msg !== "object" || !("role" in msg)) {
1030
- throw new RuntimeError6(
1031
- "RILL-R004",
1032
- "message missing required 'role' field"
1033
- );
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
1034
1152
  }
1035
- const role = msg["role"];
1036
- if (role !== "user" && role !== "assistant") {
1037
- 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
1038
1173
  }
1039
- if (!("content" in msg) || typeof msg["content"] !== "string") {
1040
- throw new RuntimeError6(
1041
- "RILL-R004",
1042
- `${role} message requires 'content'`
1043
- );
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
1044
1196
  }
1045
- messages.push({
1046
- role,
1047
- content: msg["content"]
1048
- });
1197
+ };
1198
+ },
1199
+ // Extract tool calls from OpenAI response
1200
+ extractToolCalls: (response) => {
1201
+ if (!response || typeof response !== "object" || !("choices" in response)) {
1202
+ return null;
1049
1203
  }
1050
- }
1051
- messages.push({
1052
- role: "user",
1053
- content: prompt
1054
- });
1055
- const callbacks = {
1056
- // Build OpenAI Tool format from tool definitions
1057
- buildTools: (toolDefs) => {
1058
- return toolDefs.map((def) => ({
1059
- type: "function",
1060
- function: {
1061
- name: def.name,
1062
- description: def.description,
1063
- parameters: def.input_schema
1064
- }
1065
- }));
1066
- },
1067
- // Call OpenAI API
1068
- callAPI: async (msgs, tools) => {
1069
- const apiParams = {
1070
- model: factoryModel,
1071
- max_completion_tokens: maxTokens,
1072
- messages: msgs,
1073
- tools,
1074
- tool_choice: "auto"
1075
- };
1076
- if (factoryTemperature !== void 0) {
1077
- 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 = {};
1078
1231
  }
1079
- const response2 = await client.chat.completions.create(apiParams);
1080
1232
  return {
1081
- ...response2,
1082
- usage: {
1083
- input_tokens: response2.usage?.prompt_tokens ?? 0,
1084
- output_tokens: response2.usage?.completion_tokens ?? 0
1085
- }
1233
+ id: tc.id,
1234
+ name: functionCall.function.name,
1235
+ input: parsedArgs
1086
1236
  };
1087
- },
1088
- // Extract tool calls from OpenAI response
1089
- extractToolCalls: (response2) => {
1090
- if (!response2 || typeof response2 !== "object" || !("choices" in response2)) {
1091
- return null;
1092
- }
1093
- const choices = response2.choices;
1094
- if (!Array.isArray(choices) || choices.length === 0) {
1095
- return null;
1096
- }
1097
- const choice = choices[0];
1098
- if (!choice || typeof choice !== "object" || !("message" in choice)) {
1099
- return null;
1100
- }
1101
- const message = choice.message;
1102
- if (!message || typeof message !== "object" || !("tool_calls" in message)) {
1103
- return null;
1104
- }
1105
- const toolCalls = message.tool_calls;
1106
- if (!toolCalls || !Array.isArray(toolCalls)) {
1107
- return null;
1108
- }
1109
- const functionToolCalls = toolCalls.filter(
1110
- (tc) => typeof tc === "object" && tc !== null && "type" in tc && tc.type === "function"
1111
- );
1112
- return functionToolCalls.map((tc) => {
1113
- const functionCall = tc;
1114
- const args2 = functionCall.function.arguments;
1115
- let parsedArgs;
1116
- try {
1117
- parsedArgs = JSON.parse(args2);
1118
- } catch {
1119
- parsedArgs = {};
1120
- }
1121
- return {
1122
- id: tc.id,
1123
- name: functionCall.function.name,
1124
- input: parsedArgs
1125
- };
1126
- });
1127
- },
1128
- // Extract assistant message (with tool_calls) from OpenAI response
1129
- formatAssistantMessage: (response2) => {
1130
- if (!response2 || typeof response2 !== "object" || !("choices" in response2)) {
1131
- return null;
1132
- }
1133
- const choices = response2.choices;
1134
- if (!Array.isArray(choices) || choices.length === 0) {
1135
- return null;
1136
- }
1137
- const choice = choices[0];
1138
- if (!choice || typeof choice !== "object" || !("message" in choice)) {
1139
- return null;
1140
- }
1141
- return choice.message;
1142
- },
1143
- // Format tool results into OpenAI message format
1144
- formatToolResult: (toolResults) => {
1145
- return toolResults.map((tr) => ({
1146
- role: "tool",
1147
- tool_call_id: tr.id,
1148
- content: tr.error ? JSON.stringify({ error: tr.error, code: "RILL-R001" }) : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
1149
- }));
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;
1150
1243
  }
1151
- };
1152
- const loopResult = await executeToolLoop(
1153
- messages,
1154
- options["tools"],
1155
- maxErrors,
1156
- callbacks,
1157
- (event, data) => {
1158
- const eventMap = {
1159
- tool_call: "openai:tool_call",
1160
- tool_result: "openai:tool_result"
1161
- };
1162
- emitExtensionEvent(ctx, {
1163
- event: eventMap[event] || event,
1164
- subsystem: "extension:openai",
1165
- ...data
1166
- });
1167
- },
1168
- maxTurns,
1169
- ctx
1170
- );
1171
- const response = loopResult.response;
1172
- const content = response?.choices[0]?.message?.content ?? "";
1173
- const stopReason = loopResult.turns >= maxTurns ? "max_turns" : response?.choices[0]?.finish_reason ?? "stop";
1174
- const fullMessages = [];
1175
- for (const msg of messages) {
1176
- if ("role" in msg && msg.role !== "system") {
1177
- const historyMsg = {
1178
- role: msg.role
1179
- };
1180
- if ("content" in msg && msg.content) {
1181
- historyMsg["content"] = msg.content;
1182
- }
1183
- if ("tool_calls" in msg && msg.tool_calls) {
1184
- historyMsg["tool_calls"] = msg.tool_calls;
1185
- }
1186
- fullMessages.push(historyMsg);
1244
+ const choices = response.choices;
1245
+ if (!Array.isArray(choices) || choices.length === 0) {
1246
+ return null;
1187
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
+ }));
1188
1261
  }
1189
- if (response) {
1190
- fullMessages.push({
1191
- role: "assistant",
1192
- 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
1193
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;
1194
1298
  }
1195
- const result2 = {
1196
- content,
1197
- model: factoryModel,
1198
- usage: {
1199
- input: loopResult.totalTokens.input,
1200
- output: loopResult.totalTokens.output
1201
- },
1202
- stop_reason: stopReason,
1203
- turns: loopResult.turns,
1204
- messages: fullMessages
1205
- };
1206
- const duration = Date.now() - startTime;
1207
- emitExtensionEvent(ctx, {
1208
- event: "openai:tool_loop",
1209
- subsystem: "extension:openai",
1210
- turns: loopResult.turns,
1211
- total_duration: duration,
1212
- usage: result2.usage,
1213
- request: messages,
1214
- content
1215
- });
1216
- return result2;
1217
- } catch (error) {
1218
- const duration = Date.now() - startTime;
1219
- const rillError = mapProviderError(
1220
- "OpenAI",
1221
- error,
1222
- detectOpenAIError
1223
- );
1224
- emitExtensionEvent(ctx, {
1225
- event: "openai:error",
1226
- subsystem: "extension:openai",
1227
- error: rillError.message,
1228
- duration
1229
- });
1230
- throw rillError;
1231
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
+ });
1232
1368
  },
1233
1369
  annotations: { description: "Execute tool-use loop with OpenAI API" },
1234
- returnType: rillTypeToTypeValue({
1235
- type: "dict",
1236
- fields: {
1237
- content: { type: { type: "string" } },
1238
- model: { type: { type: "string" } },
1239
- usage: { type: { type: "dict", fields: { input: { type: { type: "number" } }, output: { type: { type: "number" } } } } },
1240
- stop_reason: { type: { type: "string" } },
1241
- turns: { type: { type: "number" } },
1242
- messages: { type: { type: "list", element: { type: "dict" } } }
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
+ }
1243
1383
  }
1244
1384
  })
1245
1385
  },
@@ -1248,10 +1388,10 @@ function createOpenAIExtension(config) {
1248
1388
  params: [
1249
1389
  p.str("prompt"),
1250
1390
  p.dict("options", void 0, {}, {
1251
- schema: { type: { type: "dict" } },
1252
- system: { type: { type: "string" }, defaultValue: "" },
1253
- max_tokens: { type: { type: "number" }, defaultValue: 0 },
1254
- messages: { type: { type: "list", element: { type: "dict", fields: { role: { type: { type: "string" } }, content: { type: { type: "string" } } } } }, defaultValue: [] }
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: [] }
1255
1395
  })
1256
1396
  ],
1257
1397
  fn: async (args, ctx) => {
@@ -1268,7 +1408,7 @@ function createOpenAIExtension(config) {
1268
1408
  const rillSchema = options["schema"];
1269
1409
  const jsonSchema = buildJsonSchema(rillSchema);
1270
1410
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1271
- 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;
1272
1412
  const apiMessages = [];
1273
1413
  if (system !== void 0) {
1274
1414
  apiMessages.push({
@@ -1330,7 +1470,7 @@ function createOpenAIExtension(config) {
1330
1470
  `generate: failed to parse response JSON: ${detail}`
1331
1471
  );
1332
1472
  }
1333
- const result2 = {
1473
+ const result = {
1334
1474
  data,
1335
1475
  raw,
1336
1476
  model: response.model,
@@ -1347,11 +1487,11 @@ function createOpenAIExtension(config) {
1347
1487
  subsystem: "extension:openai",
1348
1488
  duration,
1349
1489
  model: response.model,
1350
- usage: result2.usage,
1490
+ usage: result.usage,
1351
1491
  request: apiMessages,
1352
1492
  content: raw
1353
1493
  });
1354
- return result2;
1494
+ return result;
1355
1495
  } catch (error) {
1356
1496
  const duration = Date.now() - startTime;
1357
1497
  const rillError = error instanceof RuntimeError6 ? error : mapProviderError("OpenAI", error, detectOpenAIError);
@@ -1365,21 +1505,28 @@ function createOpenAIExtension(config) {
1365
1505
  }
1366
1506
  },
1367
1507
  annotations: { description: "Generate structured output from OpenAI API" },
1368
- returnType: rillTypeToTypeValue({
1369
- type: "dict",
1508
+ returnType: structureToTypeValue({
1509
+ kind: "dict",
1370
1510
  fields: {
1371
- data: { type: { type: "any" } },
1372
- raw: { type: { type: "string" } },
1373
- model: { type: { type: "string" } },
1374
- usage: { type: { type: "dict", fields: { input: { type: { type: "number" } }, output: { type: { type: "number" } } } } },
1375
- stop_reason: { type: { type: "string" } },
1376
- id: { type: { type: "string" } }
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" } }
1377
1517
  }
1378
1518
  })
1379
1519
  }
1380
1520
  };
1381
- result.dispose = dispose;
1382
- 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 };
1383
1530
  }
1384
1531
 
1385
1532
  // src/index.ts