@rcrsr/rill-ext-gemini 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,3 +1,6 @@
1
+ // src/index.ts
2
+ import { createRequire } from "module";
3
+
1
4
  // src/factory.ts
2
5
  import {
3
6
  GoogleGenAI,
@@ -6,9 +9,11 @@ import {
6
9
  import {
7
10
  RuntimeError as RuntimeError6,
8
11
  emitExtensionEvent,
12
+ createRillStream,
9
13
  createVector,
10
14
  isVector,
11
- isDict as isDict2
15
+ structureToTypeValue,
16
+ toCallable
12
17
  } from "@rcrsr/rill";
13
18
 
14
19
  // ../../shared/ext-llm/dist/validation.js
@@ -48,7 +53,7 @@ function validateEmbedBatch(texts) {
48
53
  if (typeof item !== "string") {
49
54
  throw new RuntimeError("RILL-R001", "embed_batch requires list of strings");
50
55
  }
51
- if (item === "") {
56
+ if (item.trim() === "") {
52
57
  throw new RuntimeError("RILL-R001", `embed text cannot be empty at index ${i}`);
53
58
  }
54
59
  validated.push(item);
@@ -100,31 +105,34 @@ function mapRillType(rillType) {
100
105
  return jsonType;
101
106
  }
102
107
  function buildPropertyFromStructuralType(rillType) {
103
- if (rillType.type === "closure" || rillType.type === "tuple") {
104
- throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.type}`);
108
+ if (rillType.kind === "closure" || rillType.kind === "tuple") {
109
+ throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.kind}`);
105
110
  }
106
- if (rillType.type === "any") {
111
+ if (rillType.kind === "any") {
107
112
  return {};
108
113
  }
109
- if (rillType.type === "list") {
114
+ if (rillType.kind === "list") {
115
+ const listType = rillType;
110
116
  const property = { type: "array" };
111
- if (rillType.element !== void 0) {
112
- property.items = buildPropertyFromStructuralType(rillType.element);
117
+ if (listType.element !== void 0) {
118
+ property.items = buildPropertyFromStructuralType(listType.element);
113
119
  }
114
120
  return property;
115
121
  }
116
- if (rillType.type === "dict") {
122
+ if (rillType.kind === "dict") {
117
123
  return { type: "object" };
118
124
  }
119
- return { type: mapRillType(rillType.type) };
125
+ return { type: mapRillType(rillType.kind) };
120
126
  }
121
127
  function buildJsonSchemaFromStructuralType(type, params) {
122
128
  const properties = {};
123
129
  const required = [];
124
- if (type.type === "closure") {
130
+ if (type.kind === "closure") {
125
131
  const closureParams = type.params ?? [];
126
132
  for (let i = 0; i < closureParams.length; i++) {
127
- const [paramName, paramType] = closureParams[i];
133
+ const fieldDef = closureParams[i];
134
+ const paramName = fieldDef.name ?? `param${i}`;
135
+ const paramType = fieldDef.type;
128
136
  const rillParam = params?.[i];
129
137
  const property = buildPropertyFromStructuralType(paramType);
130
138
  const description = rillParam?.annotations["description"];
@@ -223,21 +231,20 @@ async function executeToolCall(toolName, toolInput, tools, context) {
223
231
  throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
224
232
  }
225
233
  try {
226
- let args;
227
- if ((callable.kind === "application" || callable.kind === "script") && callable.params && callable.params.length > 0) {
228
- const params = callable.params;
229
- const inputDict = toolInput;
230
- args = params.map((param) => {
231
- const value = inputDict[param.name];
232
- return value !== void 0 ? value : void 0;
233
- });
234
- } else {
235
- args = [toolInput];
236
- }
234
+ const inputDict = toolInput;
237
235
  if (callable.kind === "script") {
238
236
  if (!context) {
239
237
  throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
240
238
  }
239
+ let args;
240
+ if (callable.params && callable.params.length > 0) {
241
+ args = callable.params.map((param) => {
242
+ const value = inputDict[param.name];
243
+ return value !== void 0 ? value : void 0;
244
+ });
245
+ } else {
246
+ args = [];
247
+ }
241
248
  return await invokeCallable(callable, args, context);
242
249
  }
243
250
  const ctx = context ?? {
@@ -245,7 +252,7 @@ async function executeToolCall(toolName, toolInput, tools, context) {
245
252
  variables: /* @__PURE__ */ new Map(),
246
253
  pipeValue: null
247
254
  };
248
- const result = callable.fn(args, ctx);
255
+ const result = callable.fn(inputDict, ctx);
249
256
  return result instanceof Promise ? await result : result;
250
257
  } catch (error) {
251
258
  if (error instanceof RuntimeError4) {
@@ -321,7 +328,7 @@ function patchResponseToolCallNames(response, nameMap) {
321
328
  }
322
329
  }
323
330
  }
324
- async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context) {
331
+ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context, yieldChunk, signal) {
325
332
  if (tools === void 0) {
326
333
  throw new RuntimeError4("RILL-R004", "tools parameter is required");
327
334
  }
@@ -338,18 +345,13 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
338
345
  throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
339
346
  }
340
347
  const callable = fnValue;
341
- let description;
342
- if (callable.kind === "script") {
343
- description = callable.annotations["description"] ?? "";
344
- } else {
345
- description = callable.description ?? "";
346
- }
348
+ const description = callable.annotations?.["description"] ?? "";
347
349
  let inputSchema;
348
350
  const params = callable.kind === "application" ? callable.params ?? [] : callable.kind === "script" ? callable.params : [];
349
351
  if (params.length > 0) {
350
352
  const closureType = {
351
- type: "closure",
352
- params: params.map((p2) => [p2.name, p2.type ?? { type: "any" }])
353
+ kind: "closure",
354
+ params: params.map((p2) => ({ name: p2.name, type: p2.type ?? { kind: "any" } }))
353
355
  };
354
356
  const builtSchema = buildJsonSchemaFromStructuralType(closureType, [...params]);
355
357
  inputSchema = {
@@ -374,10 +376,20 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
374
376
  let currentMessages = [...messages];
375
377
  let turnCount = 0;
376
378
  while (turnCount < maxTurns) {
379
+ if (signal?.aborted) {
380
+ throw new RuntimeError4("RILL-R004", "tool_loop cancelled");
381
+ }
377
382
  turnCount++;
378
383
  let response;
379
384
  try {
380
- response = await callbacks.callAPI(currentMessages, providerTools);
385
+ if (yieldChunk !== void 0 && callbacks.callAPIStreaming !== void 0) {
386
+ const onTextDelta = (text) => {
387
+ yieldChunk({ type: "text_delta", text });
388
+ };
389
+ response = await callbacks.callAPIStreaming(currentMessages, providerTools, onTextDelta, signal);
390
+ } else {
391
+ response = await callbacks.callAPI(currentMessages, providerTools, signal);
392
+ }
381
393
  } catch (error) {
382
394
  const message = error instanceof Error ? error.message : "Unknown error";
383
395
  throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
@@ -415,6 +427,13 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
415
427
  for (const toolCall of toolCalls) {
416
428
  const { id, name, input } = toolCall;
417
429
  emitEvent("tool_call", { tool_name: name, args: input });
430
+ if (yieldChunk !== void 0) {
431
+ yieldChunk({
432
+ type: "tool_call",
433
+ name,
434
+ args: input
435
+ });
436
+ }
418
437
  const toolStartTime = Date.now();
419
438
  try {
420
439
  const result = await executeToolCall(name, input, tools, context);
@@ -422,6 +441,9 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
422
441
  toolResults.push({ id, name, result });
423
442
  executedToolCalls.push({ name, result });
424
443
  consecutiveErrors = 0;
444
+ if (yieldChunk !== void 0) {
445
+ yieldChunk({ type: "tool_result", name, result });
446
+ }
425
447
  emitEvent("tool_result", { tool_name: name, duration });
426
448
  } catch (error) {
427
449
  const duration = Date.now() - toolStartTime;
@@ -474,6 +496,9 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
474
496
  turns: turnCount
475
497
  };
476
498
  }
499
+ function buildResponseMessages(inputMessages, assistantContent) {
500
+ return [...inputMessages, { role: "assistant", content: assistantContent }];
501
+ }
477
502
 
478
503
  // ../../shared/ext-param/dist/param.js
479
504
  import { RuntimeError as RuntimeError5 } from "@rcrsr/rill";
@@ -503,7 +528,7 @@ var p = {
503
528
  validateParamName(name);
504
529
  return {
505
530
  name,
506
- type: { type: "string" },
531
+ type: { kind: "string" },
507
532
  defaultValue: void 0,
508
533
  annotations: buildAnnotations(desc)
509
534
  };
@@ -520,7 +545,7 @@ var p = {
520
545
  validateParamName(name);
521
546
  return {
522
547
  name,
523
- type: { type: "number" },
548
+ type: { kind: "number" },
524
549
  defaultValue: def,
525
550
  annotations: buildAnnotations(desc)
526
551
  };
@@ -537,7 +562,7 @@ var p = {
537
562
  validateParamName(name);
538
563
  return {
539
564
  name,
540
- type: { type: "bool" },
565
+ type: { kind: "bool" },
541
566
  defaultValue: def,
542
567
  annotations: buildAnnotations(desc)
543
568
  };
@@ -548,13 +573,15 @@ var p = {
548
573
  * @param name - Parameter name (must be a valid identifier)
549
574
  * @param desc - Optional description
550
575
  * @param def - Optional default value
551
- * @returns RillParam with type 'dict'
576
+ * @param fields - Optional structural field definitions (RillFieldDef with type and optional defaultValue)
577
+ * @returns RillParam with type 'dict' (with fields if provided)
552
578
  */
553
- dict(name, desc, def) {
579
+ dict(name, desc, def, fields) {
554
580
  validateParamName(name);
581
+ const type = fields !== void 0 ? { kind: "dict", fields } : { kind: "dict" };
555
582
  return {
556
583
  name,
557
- type: { type: "dict" },
584
+ type,
558
585
  defaultValue: def,
559
586
  annotations: buildAnnotations(desc)
560
587
  };
@@ -569,7 +596,7 @@ var p = {
569
596
  */
570
597
  list(name, itemType, desc) {
571
598
  validateParamName(name);
572
- const type = itemType !== void 0 ? { type: "list", element: itemType } : { type: "list" };
599
+ const type = itemType !== void 0 ? { kind: "list", element: itemType } : { kind: "list" };
573
600
  return {
574
601
  name,
575
602
  type,
@@ -588,7 +615,7 @@ var p = {
588
615
  validateParamName(name);
589
616
  return {
590
617
  name,
591
- type: { type: "closure" },
618
+ type: { kind: "closure" },
592
619
  defaultValue: void 0,
593
620
  annotations: buildAnnotations(desc)
594
621
  };
@@ -671,211 +698,314 @@ function createGeminiExtension(config) {
671
698
  console.warn(`Failed to cleanup Gemini SDK: ${message}`);
672
699
  }
673
700
  };
674
- const result = {
701
+ const fnDict = {
675
702
  // IR-4: gemini::message
676
703
  message: {
677
704
  params: [
678
705
  p.str("text"),
679
- p.dict("options", void 0, {})
706
+ p.dict("options", void 0, {}, {
707
+ system: { type: { kind: "string" }, defaultValue: "" },
708
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 }
709
+ })
680
710
  ],
681
711
  fn: async (args, ctx) => {
682
- const startTime = Date.now();
683
- try {
684
- const text = args[0];
685
- const options = args[1] ?? {};
686
- if (text.trim().length === 0) {
687
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
712
+ const text = args["text"];
713
+ const options = args["options"] ?? {};
714
+ if (text.trim().length === 0) {
715
+ throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
716
+ }
717
+ const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
718
+ const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
719
+ const contents = [
720
+ {
721
+ role: "user",
722
+ parts: [{ text }]
688
723
  }
689
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
690
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
691
- const contents = [
692
- {
693
- role: "user",
694
- parts: [{ text }]
724
+ ];
725
+ const apiConfig = {};
726
+ if (system !== void 0) {
727
+ apiConfig.systemInstruction = system;
728
+ }
729
+ if (maxTokens !== void 0) {
730
+ apiConfig.maxOutputTokens = maxTokens;
731
+ }
732
+ if (factoryTemperature !== void 0) {
733
+ apiConfig.temperature = factoryTemperature;
734
+ }
735
+ const collectedChunks = [];
736
+ let streamError;
737
+ const streamAbortController = new AbortController();
738
+ const messageStartTime = Date.now();
739
+ async function* streamChunks() {
740
+ try {
741
+ const stream = await client.models.generateContentStream({
742
+ model: factoryModel,
743
+ contents,
744
+ config: { ...apiConfig, abortSignal: streamAbortController.signal }
745
+ });
746
+ for await (const chunk of stream) {
747
+ const delta = chunk.text ?? "";
748
+ if (delta) {
749
+ collectedChunks.push(delta);
750
+ yield delta;
751
+ }
695
752
  }
696
- ];
697
- const apiConfig = {};
698
- if (system !== void 0) {
699
- apiConfig.systemInstruction = system;
700
- }
701
- if (maxTokens !== void 0) {
702
- apiConfig.maxOutputTokens = maxTokens;
753
+ } catch (error) {
754
+ streamError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
755
+ const duration = Date.now() - messageStartTime;
756
+ emitExtensionEvent(ctx, {
757
+ event: "gemini:error",
758
+ subsystem: "extension:gemini",
759
+ error: streamError.message,
760
+ duration
761
+ });
762
+ throw streamError;
703
763
  }
704
- if (factoryTemperature !== void 0) {
705
- apiConfig.temperature = factoryTemperature;
764
+ }
765
+ const resolve = async () => {
766
+ if (streamError) {
767
+ throw streamError;
706
768
  }
707
- const response = await client.models.generateContent({
708
- model: factoryModel,
709
- contents,
710
- config: apiConfig
711
- });
712
- const content = response.text ?? "";
713
- const result2 = {
769
+ const duration = Date.now() - messageStartTime;
770
+ const content = collectedChunks.join("");
771
+ const result = {
714
772
  content,
715
773
  model: factoryModel,
716
774
  usage: {
717
775
  input: 0,
718
- // Gemini API doesn't always provide token counts
719
776
  output: 0
720
777
  },
721
778
  stop_reason: "stop",
722
779
  id: "",
723
- // Gemini API doesn't provide request IDs in the same way
724
- messages: [
725
- ...system ? [{ role: "system", content: system }] : [],
726
- { role: "user", content: text },
727
- { role: "assistant", content }
728
- ]
780
+ messages: buildResponseMessages(
781
+ [
782
+ ...system ? [{ role: "system", content: system }] : [],
783
+ { role: "user", content: text }
784
+ ],
785
+ content
786
+ )
729
787
  };
730
- const duration = Date.now() - startTime;
731
788
  emitExtensionEvent(ctx, {
732
789
  event: "gemini:message",
733
790
  subsystem: "extension:gemini",
734
791
  duration,
735
792
  model: factoryModel,
736
- usage: result2.usage,
793
+ usage: result.usage,
737
794
  request: contents,
738
795
  content
739
796
  });
740
- return result2;
741
- } catch (error) {
742
- const duration = Date.now() - startTime;
743
- const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
744
- emitExtensionEvent(ctx, {
745
- event: "gemini:error",
746
- subsystem: "extension:gemini",
747
- error: rillError.message,
748
- duration
749
- });
750
- throw rillError;
751
- }
797
+ return result;
798
+ };
799
+ const retTypeStructure = {
800
+ kind: "dict",
801
+ fields: {
802
+ content: { type: { kind: "string" } },
803
+ model: { type: { kind: "string" } },
804
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
805
+ stop_reason: { type: { kind: "string" } },
806
+ id: { type: { kind: "string" } },
807
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
808
+ }
809
+ };
810
+ return createRillStream({
811
+ chunks: streamChunks(),
812
+ resolve,
813
+ dispose: () => {
814
+ streamAbortController.abort();
815
+ },
816
+ chunkType: { kind: "string" },
817
+ retType: retTypeStructure
818
+ });
752
819
  },
753
- description: "Send single message to Gemini API",
754
- returnType: { type: "dict" }
820
+ annotations: { description: "Send single message to Gemini API" },
821
+ returnType: structureToTypeValue({
822
+ kind: "stream",
823
+ chunk: { kind: "string" },
824
+ ret: {
825
+ kind: "dict",
826
+ fields: {
827
+ content: { type: { kind: "string" } },
828
+ model: { type: { kind: "string" } },
829
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
830
+ stop_reason: { type: { kind: "string" } },
831
+ id: { type: { kind: "string" } },
832
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
833
+ }
834
+ }
835
+ })
755
836
  },
756
837
  // IR-5: gemini::messages
757
838
  messages: {
758
839
  params: [
759
- p.list("messages"),
760
- p.dict("options", void 0, {})
840
+ p.list("messages", { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } }),
841
+ p.dict("options", void 0, {}, {
842
+ system: { type: { kind: "string" }, defaultValue: "" },
843
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 }
844
+ })
761
845
  ],
762
846
  fn: async (args, ctx) => {
763
- const startTime = Date.now();
764
- try {
765
- const messages = args[0];
766
- const options = args[1] ?? {};
767
- if (messages.length === 0) {
847
+ const inputMessages = args["messages"];
848
+ const options = args["options"] ?? {};
849
+ if (inputMessages.length === 0) {
850
+ throw new RuntimeError6(
851
+ "RILL-R004",
852
+ "messages list cannot be empty"
853
+ );
854
+ }
855
+ const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
856
+ const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
857
+ const contents = [];
858
+ for (let i = 0; i < inputMessages.length; i++) {
859
+ const msg = inputMessages[i];
860
+ if (!msg || typeof msg !== "object" || !("role" in msg)) {
768
861
  throw new RuntimeError6(
769
862
  "RILL-R004",
770
- "messages list cannot be empty"
863
+ "message missing required 'role' field"
771
864
  );
772
865
  }
773
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
774
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
775
- const contents = [];
776
- for (let i = 0; i < messages.length; i++) {
777
- const msg = messages[i];
778
- if (!msg || typeof msg !== "object" || !("role" in msg)) {
866
+ const role = msg["role"];
867
+ if (role !== "user" && role !== "assistant" && role !== "tool") {
868
+ throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
869
+ }
870
+ if (role === "user" || role === "tool") {
871
+ if (!("content" in msg) || typeof msg["content"] !== "string") {
779
872
  throw new RuntimeError6(
780
873
  "RILL-R004",
781
- "message missing required 'role' field"
874
+ `${role} message requires 'content'`
782
875
  );
783
876
  }
784
- const role = msg["role"];
785
- if (role !== "user" && role !== "assistant" && role !== "tool") {
786
- throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
877
+ contents.push({
878
+ role: "user",
879
+ parts: [{ text: msg["content"] }]
880
+ });
881
+ } else if (role === "assistant") {
882
+ const hasContent = "content" in msg && msg["content"];
883
+ const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
884
+ if (!hasContent && !hasToolCalls) {
885
+ throw new RuntimeError6(
886
+ "RILL-R004",
887
+ "assistant message requires 'content' or 'tool_calls'"
888
+ );
787
889
  }
788
- if (role === "user" || role === "tool") {
789
- if (!("content" in msg) || typeof msg["content"] !== "string") {
790
- throw new RuntimeError6(
791
- "RILL-R004",
792
- `${role} message requires 'content'`
793
- );
794
- }
890
+ if (hasContent) {
795
891
  contents.push({
796
- role: "user",
892
+ role: "model",
797
893
  parts: [{ text: msg["content"] }]
798
894
  });
799
- } else if (role === "assistant") {
800
- const hasContent = "content" in msg && msg["content"];
801
- const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
802
- if (!hasContent && !hasToolCalls) {
803
- throw new RuntimeError6(
804
- "RILL-R004",
805
- "assistant message requires 'content' or 'tool_calls'"
806
- );
807
- }
808
- if (hasContent) {
809
- contents.push({
810
- role: "model",
811
- parts: [{ text: msg["content"] }]
812
- });
813
- }
814
895
  }
815
896
  }
816
- const apiConfig = {};
817
- if (system !== void 0) {
818
- apiConfig.systemInstruction = system;
819
- }
820
- if (maxTokens !== void 0) {
821
- apiConfig.maxOutputTokens = maxTokens;
897
+ }
898
+ const apiConfig = {};
899
+ if (system !== void 0) {
900
+ apiConfig.systemInstruction = system;
901
+ }
902
+ if (maxTokens !== void 0) {
903
+ apiConfig.maxOutputTokens = maxTokens;
904
+ }
905
+ if (factoryTemperature !== void 0) {
906
+ apiConfig.temperature = factoryTemperature;
907
+ }
908
+ const collectedChunks = [];
909
+ let streamError;
910
+ const streamAbortController = new AbortController();
911
+ const messagesStartTime = Date.now();
912
+ async function* streamChunks() {
913
+ try {
914
+ const stream = await client.models.generateContentStream({
915
+ model: factoryModel,
916
+ contents,
917
+ config: { ...apiConfig, abortSignal: streamAbortController.signal }
918
+ });
919
+ for await (const chunk of stream) {
920
+ const delta = chunk.text ?? "";
921
+ if (delta) {
922
+ collectedChunks.push(delta);
923
+ yield delta;
924
+ }
925
+ }
926
+ } catch (error) {
927
+ streamError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
928
+ const duration = Date.now() - messagesStartTime;
929
+ emitExtensionEvent(ctx, {
930
+ event: "gemini:error",
931
+ subsystem: "extension:gemini",
932
+ error: streamError.message,
933
+ duration
934
+ });
935
+ throw streamError;
822
936
  }
823
- if (factoryTemperature !== void 0) {
824
- apiConfig.temperature = factoryTemperature;
937
+ }
938
+ const resolve = async () => {
939
+ if (streamError) {
940
+ throw streamError;
825
941
  }
826
- const response = await client.models.generateContent({
827
- model: factoryModel,
828
- contents,
829
- config: apiConfig
830
- });
831
- const content = response.text ?? "";
832
- const fullMessages = [
833
- ...messages.map((m) => {
834
- const normalized = { role: m["role"] };
835
- if ("content" in m) normalized["content"] = m["content"];
836
- if ("tool_calls" in m) normalized["tool_calls"] = m["tool_calls"];
837
- return normalized;
838
- }),
839
- { role: "assistant", content }
840
- ];
841
- const result2 = {
942
+ const duration = Date.now() - messagesStartTime;
943
+ const content = collectedChunks.join("");
944
+ const result = {
842
945
  content,
843
946
  model: factoryModel,
844
947
  usage: {
845
948
  input: 0,
846
- // Gemini API doesn't always provide token counts
847
949
  output: 0
848
950
  },
849
951
  stop_reason: "stop",
850
952
  id: "",
851
- // Gemini API doesn't provide request IDs in the same way
852
- messages: fullMessages
953
+ messages: buildResponseMessages(
954
+ inputMessages.map((m) => ({
955
+ role: m["role"],
956
+ content: m["content"] ?? ""
957
+ })),
958
+ content
959
+ )
853
960
  };
854
- const duration = Date.now() - startTime;
855
961
  emitExtensionEvent(ctx, {
856
962
  event: "gemini:messages",
857
963
  subsystem: "extension:gemini",
858
964
  duration,
859
965
  model: factoryModel,
860
- usage: result2.usage,
966
+ usage: result.usage,
861
967
  request: contents,
862
968
  content
863
969
  });
864
- return result2;
865
- } catch (error) {
866
- const duration = Date.now() - startTime;
867
- const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
868
- emitExtensionEvent(ctx, {
869
- event: "gemini:error",
870
- subsystem: "extension:gemini",
871
- error: rillError.message,
872
- duration
873
- });
874
- throw rillError;
875
- }
970
+ return result;
971
+ };
972
+ const retTypeStructure = {
973
+ kind: "dict",
974
+ fields: {
975
+ content: { type: { kind: "string" } },
976
+ model: { type: { kind: "string" } },
977
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
978
+ stop_reason: { type: { kind: "string" } },
979
+ id: { type: { kind: "string" } },
980
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
981
+ }
982
+ };
983
+ return createRillStream({
984
+ chunks: streamChunks(),
985
+ resolve,
986
+ dispose: () => {
987
+ streamAbortController.abort();
988
+ },
989
+ chunkType: { kind: "string" },
990
+ retType: retTypeStructure
991
+ });
876
992
  },
877
- description: "Send multi-turn conversation to Gemini API",
878
- returnType: { type: "dict" }
993
+ annotations: { description: "Send multi-turn conversation to Gemini API" },
994
+ returnType: structureToTypeValue({
995
+ kind: "stream",
996
+ chunk: { kind: "string" },
997
+ ret: {
998
+ kind: "dict",
999
+ fields: {
1000
+ content: { type: { kind: "string" } },
1001
+ model: { type: { kind: "string" } },
1002
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
1003
+ stop_reason: { type: { kind: "string" } },
1004
+ id: { type: { kind: "string" } },
1005
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
1006
+ }
1007
+ }
1008
+ })
879
1009
  },
880
1010
  // IR-6: gemini::embed
881
1011
  embed: {
@@ -883,7 +1013,7 @@ function createGeminiExtension(config) {
883
1013
  fn: async (args, ctx) => {
884
1014
  const startTime = Date.now();
885
1015
  try {
886
- const text = args[0];
1016
+ const text = args["text"];
887
1017
  validateEmbedText(text);
888
1018
  validateEmbedModel(factoryEmbedModel);
889
1019
  const response = await client.models.embedContent({
@@ -920,8 +1050,8 @@ function createGeminiExtension(config) {
920
1050
  throw rillError;
921
1051
  }
922
1052
  },
923
- description: "Generate embedding vector for text",
924
- returnType: { type: "vector" }
1053
+ annotations: { description: "Generate embedding vector for text" },
1054
+ returnType: structureToTypeValue({ kind: "vector" })
925
1055
  },
926
1056
  // IR-7: gemini::embed_batch
927
1057
  embed_batch: {
@@ -929,7 +1059,7 @@ function createGeminiExtension(config) {
929
1059
  fn: async (args, ctx) => {
930
1060
  const startTime = Date.now();
931
1061
  try {
932
- const texts = args[0];
1062
+ const texts = args["texts"];
933
1063
  if (texts.length === 0) {
934
1064
  return [];
935
1065
  }
@@ -981,225 +1111,367 @@ function createGeminiExtension(config) {
981
1111
  throw rillError;
982
1112
  }
983
1113
  },
984
- description: "Generate embedding vectors for multiple texts",
985
- returnType: { type: "list" }
1114
+ annotations: { description: "Generate embedding vectors for multiple texts" },
1115
+ returnType: structureToTypeValue({ kind: "list", element: { kind: "vector" } })
986
1116
  },
987
1117
  // IR-8: gemini::tool_loop
988
1118
  tool_loop: {
989
1119
  params: [
990
1120
  p.str("prompt"),
991
- p.dict("options", void 0, {})
1121
+ {
1122
+ name: "tools",
1123
+ type: { kind: "dict", valueType: { kind: "closure" } },
1124
+ defaultValue: void 0,
1125
+ annotations: {}
1126
+ },
1127
+ p.dict("options", void 0, void 0, {
1128
+ system: { type: { kind: "string" }, defaultValue: "" },
1129
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 },
1130
+ max_errors: { type: { kind: "number" }, defaultValue: 3 },
1131
+ max_turns: { type: { kind: "number" }, defaultValue: 10 },
1132
+ messages: { type: { kind: "list", element: { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } } }, defaultValue: [] }
1133
+ })
992
1134
  ],
993
- fn: async (args, ctx) => {
994
- const startTime = Date.now();
995
- try {
996
- const prompt = args[0];
997
- const options = args[1] ?? {};
998
- if (prompt.trim().length === 0) {
999
- throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
1135
+ fn: (args, ctx) => {
1136
+ const prompt = args["prompt"];
1137
+ const toolsDict = args["tools"];
1138
+ const options = args["options"] ?? {};
1139
+ if (prompt.trim().length === 0) {
1140
+ throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
1141
+ }
1142
+ const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1143
+ const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
1144
+ const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
1145
+ const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
1146
+ const initialMessages = Array.isArray(options["messages"]) && options["messages"].length > 0 ? options["messages"] : [];
1147
+ const contents = [];
1148
+ for (const msg of initialMessages) {
1149
+ if (typeof msg === "object" && msg !== null && "role" in msg && "content" in msg) {
1150
+ const role = msg["role"];
1151
+ if (role === "user") {
1152
+ contents.push({
1153
+ role: "user",
1154
+ parts: [{ text: msg["content"] }]
1155
+ });
1156
+ } else if (role === "assistant") {
1157
+ contents.push({
1158
+ role: "model",
1159
+ parts: [{ text: msg["content"] }]
1160
+ });
1161
+ }
1000
1162
  }
1001
- if (!("tools" in options) || !isDict2(options["tools"])) {
1002
- throw new RuntimeError6(
1003
- "RILL-R004",
1004
- "tool_loop requires 'tools' option"
1005
- );
1163
+ }
1164
+ contents.push({
1165
+ role: "user",
1166
+ parts: [{ text: prompt }]
1167
+ });
1168
+ const apiConfig = {
1169
+ ...system !== void 0 && { systemInstruction: system },
1170
+ ...maxTokens !== void 0 && { maxOutputTokens: maxTokens },
1171
+ ...factoryTemperature !== void 0 && {
1172
+ temperature: factoryTemperature
1006
1173
  }
1007
- const toolsDict = options["tools"];
1008
- const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1009
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
1010
- const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
1011
- const maxErrors = typeof options["max_errors"] === "number" ? options["max_errors"] : 3;
1012
- const initialMessages = Array.isArray(options["messages"]) && options["messages"].length > 0 ? options["messages"] : [];
1013
- const contents = [];
1014
- for (const msg of initialMessages) {
1015
- if (typeof msg === "object" && msg !== null && "role" in msg && "content" in msg) {
1016
- const role = msg["role"];
1017
- if (role === "user") {
1018
- contents.push({
1019
- role: "user",
1020
- parts: [{ text: msg["content"] }]
1021
- });
1022
- } else if (role === "assistant") {
1023
- contents.push({
1024
- role: "model",
1025
- parts: [{ text: msg["content"] }]
1026
- });
1027
- }
1174
+ };
1175
+ const buildToolDeclarations = (toolDefs) => {
1176
+ return toolDefs.map((def) => {
1177
+ const properties = {};
1178
+ for (const [propName, propDef] of Object.entries(
1179
+ def.input_schema.properties
1180
+ )) {
1181
+ const prop = propDef;
1182
+ const propType = prop["type"];
1183
+ let schemaType = Type.STRING;
1184
+ if (propType === "number") schemaType = Type.NUMBER;
1185
+ if (propType === "boolean") schemaType = Type.BOOLEAN;
1186
+ if (propType === "integer") schemaType = Type.INTEGER;
1187
+ if (propType === "array") schemaType = Type.ARRAY;
1188
+ if (propType === "object") schemaType = Type.OBJECT;
1189
+ properties[propName] = {
1190
+ type: schemaType,
1191
+ description: prop["description"] ?? ""
1192
+ };
1028
1193
  }
1029
- }
1030
- contents.push({
1031
- role: "user",
1032
- parts: [{ text: prompt }]
1194
+ return {
1195
+ name: def.name,
1196
+ description: def.description,
1197
+ parameters: {
1198
+ type: Type.OBJECT,
1199
+ properties,
1200
+ required: def.input_schema.required
1201
+ }
1202
+ };
1033
1203
  });
1034
- const callbacks = {
1035
- // Build Gemini FunctionDeclaration format from tool definitions
1036
- buildTools: (toolDefs) => {
1037
- return toolDefs.map((def) => {
1038
- const properties = {};
1039
- for (const [propName, propDef] of Object.entries(
1040
- def.input_schema.properties
1041
- )) {
1042
- const prop = propDef;
1043
- const propType = prop["type"];
1044
- let schemaType = Type.STRING;
1045
- if (propType === "number") schemaType = Type.NUMBER;
1046
- if (propType === "boolean") schemaType = Type.BOOLEAN;
1047
- if (propType === "integer") schemaType = Type.INTEGER;
1048
- if (propType === "array") schemaType = Type.ARRAY;
1049
- if (propType === "object") schemaType = Type.OBJECT;
1050
- properties[propName] = {
1051
- type: schemaType,
1052
- description: prop["description"] ?? ""
1053
- };
1054
- }
1055
- return {
1056
- name: def.name,
1057
- description: def.description,
1058
- parameters: {
1059
- type: Type.OBJECT,
1060
- properties,
1061
- required: def.input_schema.required
1062
- }
1063
- };
1064
- });
1065
- },
1066
- // Call Gemini API
1067
- callAPI: async (msgs, tools) => {
1068
- const apiConfig = {
1069
- ...system !== void 0 && { systemInstruction: system },
1070
- ...maxTokens !== void 0 && { maxOutputTokens: maxTokens },
1071
- ...factoryTemperature !== void 0 && {
1072
- temperature: factoryTemperature
1073
- },
1204
+ };
1205
+ let resolveNext;
1206
+ const chunkQueue = [];
1207
+ let streamDone = false;
1208
+ let streamError;
1209
+ let loopResultHolder;
1210
+ const accumulatedTextDeltas = [];
1211
+ const callbacks = {
1212
+ buildTools: buildToolDeclarations,
1213
+ callAPI: async (msgs, tools, signal) => {
1214
+ return await client.models.generateContent({
1215
+ model: factoryModel,
1216
+ contents: msgs,
1217
+ config: {
1218
+ ...apiConfig,
1219
+ ...signal !== void 0 && { abortSignal: signal },
1074
1220
  tools: [
1075
1221
  { functionDeclarations: tools }
1076
1222
  ]
1077
- };
1078
- return await client.models.generateContent({
1079
- model: factoryModel,
1080
- contents: msgs,
1081
- config: apiConfig
1082
- });
1083
- },
1084
- // Extract tool calls from Gemini response
1085
- extractToolCalls: (response2) => {
1086
- if (!response2 || typeof response2 !== "object" || !("functionCalls" in response2)) {
1087
- return null;
1088
- }
1089
- const functionCalls = response2.functionCalls;
1090
- if (!functionCalls || functionCalls.length === 0) {
1091
- return null;
1092
- }
1093
- return functionCalls.map((fc) => {
1094
- const call = fc;
1095
- return {
1096
- id: call.id ?? "",
1097
- name: call.name ?? "",
1098
- input: call.args ?? {}
1099
- };
1100
- });
1101
- },
1102
- // Extract the model's content from Gemini response for conversation history
1103
- formatAssistantMessage: (response2) => {
1104
- if (!response2 || typeof response2 !== "object" || !("candidates" in response2)) {
1105
- return null;
1106
1223
  }
1107
- const candidates = response2.candidates;
1108
- if (!Array.isArray(candidates) || candidates.length === 0) {
1109
- return null;
1224
+ });
1225
+ },
1226
+ // IR-3: Streaming API path
1227
+ callAPIStreaming: async (msgs, tools, onTextDelta, signal) => {
1228
+ const stream = await client.models.generateContentStream({
1229
+ model: factoryModel,
1230
+ contents: msgs,
1231
+ config: {
1232
+ ...apiConfig,
1233
+ ...signal !== void 0 && { abortSignal: signal },
1234
+ tools: [
1235
+ { functionDeclarations: tools }
1236
+ ]
1110
1237
  }
1111
- const candidate = candidates[0];
1112
- if (!candidate || typeof candidate !== "object" || !("content" in candidate)) {
1113
- return null;
1238
+ });
1239
+ let lastChunk;
1240
+ for await (const chunk of stream) {
1241
+ lastChunk = chunk;
1242
+ const delta = chunk.text ?? "";
1243
+ if (delta) {
1244
+ onTextDelta(delta);
1114
1245
  }
1115
- return candidate.content;
1116
- },
1117
- // Format tool results into Gemini message format
1118
- formatToolResult: (toolResults) => {
1119
- const functionResponseParts = toolResults.map((tr) => ({
1120
- functionResponse: {
1121
- name: tr.name,
1122
- response: {
1123
- result: tr.error ? `Error: ${tr.error}` : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
1124
- }
1125
- }
1126
- }));
1246
+ }
1247
+ return lastChunk;
1248
+ },
1249
+ extractToolCalls: (response) => {
1250
+ if (!response || typeof response !== "object" || !("functionCalls" in response)) {
1251
+ return null;
1252
+ }
1253
+ const functionCalls = response.functionCalls;
1254
+ if (!functionCalls || functionCalls.length === 0) {
1255
+ return null;
1256
+ }
1257
+ return functionCalls.map((fc) => {
1258
+ const call = fc;
1127
1259
  return {
1128
- role: "user",
1129
- parts: functionResponseParts
1260
+ id: call.id ?? "",
1261
+ name: call.name ?? "",
1262
+ input: call.args ?? {}
1130
1263
  };
1264
+ });
1265
+ },
1266
+ formatAssistantMessage: (response) => {
1267
+ if (!response || typeof response !== "object" || !("candidates" in response)) {
1268
+ return null;
1131
1269
  }
1132
- };
1133
- const loopResult = await executeToolLoop(
1134
- contents,
1135
- toolsDict,
1136
- maxErrors,
1137
- callbacks,
1138
- (event, data) => {
1139
- const eventMap = {
1140
- tool_call: "gemini:tool_call",
1141
- tool_result: "gemini:tool_result"
1142
- };
1270
+ const candidates = response.candidates;
1271
+ if (!Array.isArray(candidates) || candidates.length === 0) {
1272
+ return null;
1273
+ }
1274
+ const candidate = candidates[0];
1275
+ if (!candidate || typeof candidate !== "object" || !("content" in candidate)) {
1276
+ return null;
1277
+ }
1278
+ return candidate.content;
1279
+ },
1280
+ formatToolResult: (toolResults) => {
1281
+ const functionResponseParts = toolResults.map((tr) => ({
1282
+ functionResponse: {
1283
+ name: tr.name,
1284
+ response: {
1285
+ result: tr.error ? `Error: ${tr.error}` : typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
1286
+ }
1287
+ }
1288
+ }));
1289
+ return {
1290
+ role: "user",
1291
+ parts: functionResponseParts
1292
+ };
1293
+ }
1294
+ };
1295
+ const toolLoopAbortController = new AbortController();
1296
+ const loopPromise = executeToolLoop(
1297
+ contents,
1298
+ toolsDict,
1299
+ maxErrors,
1300
+ callbacks,
1301
+ (event, data) => {
1302
+ const eventMap = {
1303
+ tool_call: "gemini:tool_call",
1304
+ tool_result: "gemini:tool_result"
1305
+ };
1306
+ emitExtensionEvent(ctx, {
1307
+ event: eventMap[event] || event,
1308
+ subsystem: "extension:gemini",
1309
+ ...data
1310
+ });
1311
+ },
1312
+ maxTurns,
1313
+ ctx,
1314
+ // yieldChunk — called from executeToolLoop for each text_delta, tool_call,
1315
+ // or tool_result. Buffers the chunk and signals the generator to wake.
1316
+ // AC-16: Also accumulate text_delta text for partial resolve on disconnect.
1317
+ (chunk) => {
1318
+ const chunkRecord = chunk;
1319
+ if (chunkRecord["type"] === "text_delta" && typeof chunkRecord["text"] === "string") {
1320
+ accumulatedTextDeltas.push(chunkRecord["text"]);
1321
+ }
1322
+ chunkQueue.push(chunk);
1323
+ if (resolveNext) {
1324
+ const r = resolveNext;
1325
+ resolveNext = void 0;
1326
+ r();
1327
+ }
1328
+ },
1329
+ toolLoopAbortController.signal
1330
+ ).then((result) => {
1331
+ loopResultHolder = result;
1332
+ streamDone = true;
1333
+ if (resolveNext) {
1334
+ const r = resolveNext;
1335
+ resolveNext = void 0;
1336
+ r();
1337
+ }
1338
+ }).catch((error) => {
1339
+ streamError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
1340
+ streamDone = true;
1341
+ if (resolveNext) {
1342
+ const r = resolveNext;
1343
+ resolveNext = void 0;
1344
+ r();
1345
+ }
1346
+ });
1347
+ async function* streamGenerator() {
1348
+ while (true) {
1349
+ while (chunkQueue.length > 0) {
1350
+ yield chunkQueue.shift();
1351
+ }
1352
+ if (streamDone) {
1353
+ if (streamError) throw streamError;
1354
+ break;
1355
+ }
1356
+ await new Promise((resolve2) => {
1357
+ resolveNext = resolve2;
1358
+ });
1359
+ }
1360
+ }
1361
+ const inputMessages = [
1362
+ ...initialMessages.map((m) => ({
1363
+ role: m["role"],
1364
+ content: m["content"] ?? ""
1365
+ })),
1366
+ { role: "user", content: prompt }
1367
+ ];
1368
+ const retTypeStructure = {
1369
+ kind: "dict",
1370
+ fields: {
1371
+ content: { type: { kind: "string" } },
1372
+ model: { type: { kind: "string" } },
1373
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
1374
+ stop_reason: { type: { kind: "string" } },
1375
+ turns: { type: { kind: "number" } },
1376
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
1377
+ }
1378
+ };
1379
+ const resolve = async () => {
1380
+ const startTime = Date.now();
1381
+ await loopPromise;
1382
+ if (streamError) {
1383
+ if (accumulatedTextDeltas.length > 0) {
1384
+ const partialContent = accumulatedTextDeltas.join("");
1143
1385
  emitExtensionEvent(ctx, {
1144
- event: eventMap[event] || event,
1386
+ event: "gemini:error",
1145
1387
  subsystem: "extension:gemini",
1146
- ...data
1388
+ error: streamError.message,
1389
+ duration: Date.now()
1147
1390
  });
1148
- },
1149
- maxTurns,
1150
- ctx
1151
- );
1152
- const response = loopResult.response;
1391
+ return {
1392
+ content: partialContent,
1393
+ model: factoryModel,
1394
+ usage: { input: 0, output: 0 },
1395
+ stop_reason: "error",
1396
+ turns: 0,
1397
+ messages: buildResponseMessages(inputMessages, partialContent)
1398
+ };
1399
+ }
1400
+ const duration = Date.now();
1401
+ emitExtensionEvent(ctx, {
1402
+ event: "gemini:error",
1403
+ subsystem: "extension:gemini",
1404
+ error: streamError.message,
1405
+ duration
1406
+ });
1407
+ throw streamError;
1408
+ }
1409
+ const result = loopResultHolder;
1410
+ const response = result.response;
1153
1411
  const content = response && typeof response === "object" && "text" in response ? response.text ?? "" : "";
1154
- const result2 = {
1412
+ const resolvedResult = {
1155
1413
  content,
1156
1414
  model: factoryModel,
1157
- usage: loopResult.totalTokens,
1415
+ usage: result.totalTokens,
1158
1416
  stop_reason: response ? "stop" : "max_turns",
1159
- turns: loopResult.turns,
1160
- messages: [
1161
- ...initialMessages,
1162
- { role: "user", content: prompt },
1163
- { role: "assistant", content }
1164
- ]
1417
+ turns: result.turns,
1418
+ messages: response ? buildResponseMessages(inputMessages, content) : inputMessages
1165
1419
  };
1166
- const duration = Date.now() - startTime;
1420
+ const total_duration = Date.now() - startTime;
1167
1421
  emitExtensionEvent(ctx, {
1168
1422
  event: "gemini:tool_loop",
1169
1423
  subsystem: "extension:gemini",
1170
- turns: result2.turns,
1171
- total_duration: duration,
1172
- usage: result2.usage,
1424
+ turns: resolvedResult.turns,
1425
+ total_duration,
1426
+ usage: resolvedResult.usage,
1173
1427
  request: contents,
1174
1428
  content
1175
1429
  });
1176
- return result2;
1177
- } catch (error) {
1178
- const duration = Date.now() - startTime;
1179
- const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
1180
- emitExtensionEvent(ctx, {
1181
- event: "gemini:error",
1182
- subsystem: "extension:gemini",
1183
- error: rillError.message,
1184
- duration
1185
- });
1186
- throw rillError;
1187
- }
1430
+ return resolvedResult;
1431
+ };
1432
+ return createRillStream({
1433
+ chunks: streamGenerator(),
1434
+ resolve,
1435
+ dispose: () => {
1436
+ toolLoopAbortController.abort();
1437
+ },
1438
+ chunkType: { kind: "dict" },
1439
+ retType: retTypeStructure
1440
+ });
1188
1441
  },
1189
- description: "Execute tool-use loop with Gemini API",
1190
- returnType: { type: "dict" }
1442
+ annotations: { description: "Execute tool-use loop with Gemini API" },
1443
+ returnType: structureToTypeValue({
1444
+ kind: "stream",
1445
+ chunk: { kind: "dict" },
1446
+ ret: {
1447
+ kind: "dict",
1448
+ fields: {
1449
+ content: { type: { kind: "string" } },
1450
+ model: { type: { kind: "string" } },
1451
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
1452
+ stop_reason: { type: { kind: "string" } },
1453
+ turns: { type: { kind: "number" } },
1454
+ messages: { type: { kind: "list", element: { kind: "dict" } } }
1455
+ }
1456
+ }
1457
+ })
1191
1458
  },
1192
1459
  // IR-3: gemini::generate
1193
1460
  generate: {
1194
1461
  params: [
1195
1462
  p.str("prompt"),
1196
- p.dict("options")
1463
+ p.dict("options", void 0, {}, {
1464
+ schema: { type: { kind: "dict" } },
1465
+ system: { type: { kind: "string" }, defaultValue: "" },
1466
+ max_tokens: { type: { kind: "number" }, defaultValue: 0 },
1467
+ messages: { type: { kind: "list", element: { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } } }, defaultValue: [] }
1468
+ })
1197
1469
  ],
1198
1470
  fn: async (args, ctx) => {
1199
1471
  const startTime = Date.now();
1200
1472
  try {
1201
- const prompt = args[0];
1202
- const options = args[1] ?? {};
1473
+ const prompt = args["prompt"];
1474
+ const options = args["options"] ?? {};
1203
1475
  if (!("schema" in options) || options["schema"] === null || options["schema"] === void 0) {
1204
1476
  throw new RuntimeError6(
1205
1477
  "RILL-R004",
@@ -1218,7 +1490,7 @@ function createGeminiExtension(config) {
1218
1490
  required: jsonSchema.required
1219
1491
  };
1220
1492
  const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
1221
- const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
1493
+ const maxTokens = typeof options["max_tokens"] === "number" && options["max_tokens"] > 0 ? options["max_tokens"] : factoryMaxTokens;
1222
1494
  const contents = [];
1223
1495
  if ("messages" in options && Array.isArray(options["messages"])) {
1224
1496
  const prependedMessages = options["messages"];
@@ -1310,16 +1582,35 @@ function createGeminiExtension(config) {
1310
1582
  throw rillError;
1311
1583
  }
1312
1584
  },
1313
- description: "Generate structured output from Gemini API",
1314
- returnType: { type: "dict" }
1585
+ annotations: { description: "Generate structured output from Gemini API" },
1586
+ returnType: structureToTypeValue({
1587
+ kind: "dict",
1588
+ fields: {
1589
+ data: { type: { kind: "any" } },
1590
+ raw: { type: { kind: "string" } },
1591
+ model: { type: { kind: "string" } },
1592
+ usage: { type: { kind: "dict", fields: { input: { type: { kind: "number" } }, output: { type: { kind: "number" } } } } },
1593
+ stop_reason: { type: { kind: "string" } },
1594
+ id: { type: { kind: "string" } }
1595
+ }
1596
+ })
1315
1597
  }
1316
1598
  };
1317
- result.dispose = dispose;
1318
- return result;
1599
+ const callableDict = {
1600
+ message: toCallable(fnDict.message),
1601
+ messages: toCallable(fnDict.messages),
1602
+ embed: toCallable(fnDict.embed),
1603
+ embed_batch: toCallable(fnDict.embed_batch),
1604
+ tool_loop: toCallable(fnDict.tool_loop),
1605
+ generate: toCallable(fnDict.generate)
1606
+ };
1607
+ return { value: callableDict, dispose };
1319
1608
  }
1320
1609
 
1321
1610
  // src/index.ts
1322
- var VERSION = "0.0.1";
1611
+ var _require = createRequire(import.meta.url);
1612
+ var _pkg = _require("../package.json");
1613
+ var VERSION = _pkg.version;
1323
1614
  var configSchema = {
1324
1615
  api_key: { type: "string", required: true, secret: true },
1325
1616
  model: { type: "string", required: true },
@@ -1331,8 +1622,14 @@ var configSchema = {
1331
1622
  system: { type: "string" },
1332
1623
  embed_model: { type: "string" }
1333
1624
  };
1625
+ var extensionManifest = {
1626
+ factory: createGeminiExtension,
1627
+ configSchema,
1628
+ version: VERSION
1629
+ };
1334
1630
  export {
1335
1631
  VERSION,
1336
1632
  configSchema,
1337
- createGeminiExtension
1633
+ createGeminiExtension,
1634
+ extensionManifest
1338
1635
  };