@rcrsr/rill-ext-gemini 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -26
- package/dist/index.d.ts +2 -2
- package/dist/index.js +606 -377
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
RuntimeError as RuntimeError6,
|
|
11
11
|
emitExtensionEvent,
|
|
12
|
+
createRillStream,
|
|
12
13
|
createVector,
|
|
13
14
|
isVector,
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
structureToTypeValue,
|
|
16
|
+
toCallable
|
|
16
17
|
} from "@rcrsr/rill";
|
|
17
18
|
|
|
18
19
|
// ../../shared/ext-llm/dist/validation.js
|
|
@@ -52,7 +53,7 @@ function validateEmbedBatch(texts) {
|
|
|
52
53
|
if (typeof item !== "string") {
|
|
53
54
|
throw new RuntimeError("RILL-R001", "embed_batch requires list of strings");
|
|
54
55
|
}
|
|
55
|
-
if (item === "") {
|
|
56
|
+
if (item.trim() === "") {
|
|
56
57
|
throw new RuntimeError("RILL-R001", `embed text cannot be empty at index ${i}`);
|
|
57
58
|
}
|
|
58
59
|
validated.push(item);
|
|
@@ -104,28 +105,29 @@ function mapRillType(rillType) {
|
|
|
104
105
|
return jsonType;
|
|
105
106
|
}
|
|
106
107
|
function buildPropertyFromStructuralType(rillType) {
|
|
107
|
-
if (rillType.
|
|
108
|
-
throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.
|
|
108
|
+
if (rillType.kind === "closure" || rillType.kind === "tuple") {
|
|
109
|
+
throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.kind}`);
|
|
109
110
|
}
|
|
110
|
-
if (rillType.
|
|
111
|
+
if (rillType.kind === "any") {
|
|
111
112
|
return {};
|
|
112
113
|
}
|
|
113
|
-
if (rillType.
|
|
114
|
+
if (rillType.kind === "list") {
|
|
115
|
+
const listType = rillType;
|
|
114
116
|
const property = { type: "array" };
|
|
115
|
-
if (
|
|
116
|
-
property.items = buildPropertyFromStructuralType(
|
|
117
|
+
if (listType.element !== void 0) {
|
|
118
|
+
property.items = buildPropertyFromStructuralType(listType.element);
|
|
117
119
|
}
|
|
118
120
|
return property;
|
|
119
121
|
}
|
|
120
|
-
if (rillType.
|
|
122
|
+
if (rillType.kind === "dict") {
|
|
121
123
|
return { type: "object" };
|
|
122
124
|
}
|
|
123
|
-
return { type: mapRillType(rillType.
|
|
125
|
+
return { type: mapRillType(rillType.kind) };
|
|
124
126
|
}
|
|
125
127
|
function buildJsonSchemaFromStructuralType(type, params) {
|
|
126
128
|
const properties = {};
|
|
127
129
|
const required = [];
|
|
128
|
-
if (type.
|
|
130
|
+
if (type.kind === "closure") {
|
|
129
131
|
const closureParams = type.params ?? [];
|
|
130
132
|
for (let i = 0; i < closureParams.length; i++) {
|
|
131
133
|
const fieldDef = closureParams[i];
|
|
@@ -241,7 +243,7 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
241
243
|
return value !== void 0 ? value : void 0;
|
|
242
244
|
});
|
|
243
245
|
} else {
|
|
244
|
-
args = [
|
|
246
|
+
args = [];
|
|
245
247
|
}
|
|
246
248
|
return await invokeCallable(callable, args, context);
|
|
247
249
|
}
|
|
@@ -326,7 +328,7 @@ function patchResponseToolCallNames(response, nameMap) {
|
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
330
|
}
|
|
329
|
-
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) {
|
|
330
332
|
if (tools === void 0) {
|
|
331
333
|
throw new RuntimeError4("RILL-R004", "tools parameter is required");
|
|
332
334
|
}
|
|
@@ -348,8 +350,8 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
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
|
-
|
|
352
|
-
params: params.map((p2) => ({ name: p2.name, type: p2.type ?? {
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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: {
|
|
565
|
+
type: { kind: "bool" },
|
|
541
566
|
defaultValue: def,
|
|
542
567
|
annotations: buildAnnotations(desc)
|
|
543
568
|
};
|
|
@@ -553,7 +578,7 @@ var p = {
|
|
|
553
578
|
*/
|
|
554
579
|
dict(name, desc, def, fields) {
|
|
555
580
|
validateParamName(name);
|
|
556
|
-
const type = fields !== void 0 ? {
|
|
581
|
+
const type = fields !== void 0 ? { kind: "dict", fields } : { kind: "dict" };
|
|
557
582
|
return {
|
|
558
583
|
name,
|
|
559
584
|
type,
|
|
@@ -571,7 +596,7 @@ var p = {
|
|
|
571
596
|
*/
|
|
572
597
|
list(name, itemType, desc) {
|
|
573
598
|
validateParamName(name);
|
|
574
|
-
const type = itemType !== void 0 ? {
|
|
599
|
+
const type = itemType !== void 0 ? { kind: "list", element: itemType } : { kind: "list" };
|
|
575
600
|
return {
|
|
576
601
|
name,
|
|
577
602
|
type,
|
|
@@ -590,7 +615,7 @@ var p = {
|
|
|
590
615
|
validateParamName(name);
|
|
591
616
|
return {
|
|
592
617
|
name,
|
|
593
|
-
type: {
|
|
618
|
+
type: { kind: "closure" },
|
|
594
619
|
defaultValue: void 0,
|
|
595
620
|
annotations: buildAnnotations(desc)
|
|
596
621
|
};
|
|
@@ -673,235 +698,312 @@ function createGeminiExtension(config) {
|
|
|
673
698
|
console.warn(`Failed to cleanup Gemini SDK: ${message}`);
|
|
674
699
|
}
|
|
675
700
|
};
|
|
676
|
-
const
|
|
701
|
+
const fnDict = {
|
|
677
702
|
// IR-4: gemini::message
|
|
678
703
|
message: {
|
|
679
704
|
params: [
|
|
680
705
|
p.str("text"),
|
|
681
706
|
p.dict("options", void 0, {}, {
|
|
682
|
-
system: { type: {
|
|
683
|
-
max_tokens: { type: {
|
|
707
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
708
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 }
|
|
684
709
|
})
|
|
685
710
|
],
|
|
686
711
|
fn: async (args, ctx) => {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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 }]
|
|
693
723
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
+
}
|
|
700
752
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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;
|
|
708
763
|
}
|
|
709
|
-
|
|
710
|
-
|
|
764
|
+
}
|
|
765
|
+
const resolve = async () => {
|
|
766
|
+
if (streamError) {
|
|
767
|
+
throw streamError;
|
|
711
768
|
}
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
config: apiConfig
|
|
716
|
-
});
|
|
717
|
-
const content = response.text ?? "";
|
|
718
|
-
const result2 = {
|
|
769
|
+
const duration = Date.now() - messageStartTime;
|
|
770
|
+
const content = collectedChunks.join("");
|
|
771
|
+
const result = {
|
|
719
772
|
content,
|
|
720
773
|
model: factoryModel,
|
|
721
774
|
usage: {
|
|
722
775
|
input: 0,
|
|
723
|
-
// Gemini API doesn't always provide token counts
|
|
724
776
|
output: 0
|
|
725
777
|
},
|
|
726
778
|
stop_reason: "stop",
|
|
727
779
|
id: "",
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
780
|
+
messages: buildResponseMessages(
|
|
781
|
+
[
|
|
782
|
+
...system ? [{ role: "system", content: system }] : [],
|
|
783
|
+
{ role: "user", content: text }
|
|
784
|
+
],
|
|
785
|
+
content
|
|
786
|
+
)
|
|
734
787
|
};
|
|
735
|
-
const duration = Date.now() - startTime;
|
|
736
788
|
emitExtensionEvent(ctx, {
|
|
737
789
|
event: "gemini:message",
|
|
738
790
|
subsystem: "extension:gemini",
|
|
739
791
|
duration,
|
|
740
792
|
model: factoryModel,
|
|
741
|
-
usage:
|
|
793
|
+
usage: result.usage,
|
|
742
794
|
request: contents,
|
|
743
795
|
content
|
|
744
796
|
});
|
|
745
|
-
return
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
});
|
|
757
819
|
},
|
|
758
820
|
annotations: { description: "Send single message to Gemini API" },
|
|
759
|
-
returnType:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
768
834
|
}
|
|
769
835
|
})
|
|
770
836
|
},
|
|
771
837
|
// IR-5: gemini::messages
|
|
772
838
|
messages: {
|
|
773
839
|
params: [
|
|
774
|
-
p.list("messages", {
|
|
840
|
+
p.list("messages", { kind: "dict", fields: { role: { type: { kind: "string" } }, content: { type: { kind: "string" } } } }),
|
|
775
841
|
p.dict("options", void 0, {}, {
|
|
776
|
-
system: { type: {
|
|
777
|
-
max_tokens: { type: {
|
|
842
|
+
system: { type: { kind: "string" }, defaultValue: "" },
|
|
843
|
+
max_tokens: { type: { kind: "number" }, defaultValue: 0 }
|
|
778
844
|
})
|
|
779
845
|
],
|
|
780
846
|
fn: async (args, ctx) => {
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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)) {
|
|
786
861
|
throw new RuntimeError6(
|
|
787
862
|
"RILL-R004",
|
|
788
|
-
"
|
|
863
|
+
"message missing required 'role' field"
|
|
789
864
|
);
|
|
790
865
|
}
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (!msg || typeof 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") {
|
|
797
872
|
throw new RuntimeError6(
|
|
798
873
|
"RILL-R004",
|
|
799
|
-
|
|
874
|
+
`${role} message requires 'content'`
|
|
800
875
|
);
|
|
801
876
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
+
);
|
|
805
889
|
}
|
|
806
|
-
if (
|
|
807
|
-
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
808
|
-
throw new RuntimeError6(
|
|
809
|
-
"RILL-R004",
|
|
810
|
-
`${role} message requires 'content'`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
890
|
+
if (hasContent) {
|
|
813
891
|
contents.push({
|
|
814
|
-
role: "
|
|
892
|
+
role: "model",
|
|
815
893
|
parts: [{ text: msg["content"] }]
|
|
816
894
|
});
|
|
817
|
-
} else if (role === "assistant") {
|
|
818
|
-
const hasContent = "content" in msg && msg["content"];
|
|
819
|
-
const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
|
|
820
|
-
if (!hasContent && !hasToolCalls) {
|
|
821
|
-
throw new RuntimeError6(
|
|
822
|
-
"RILL-R004",
|
|
823
|
-
"assistant message requires 'content' or 'tool_calls'"
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
if (hasContent) {
|
|
827
|
-
contents.push({
|
|
828
|
-
role: "model",
|
|
829
|
-
parts: [{ text: msg["content"] }]
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
895
|
}
|
|
833
896
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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;
|
|
840
936
|
}
|
|
841
|
-
|
|
842
|
-
|
|
937
|
+
}
|
|
938
|
+
const resolve = async () => {
|
|
939
|
+
if (streamError) {
|
|
940
|
+
throw streamError;
|
|
843
941
|
}
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
config: apiConfig
|
|
848
|
-
});
|
|
849
|
-
const content = response.text ?? "";
|
|
850
|
-
const fullMessages = [
|
|
851
|
-
...messages.map((m) => {
|
|
852
|
-
const normalized = { role: m["role"] };
|
|
853
|
-
if ("content" in m) normalized["content"] = m["content"];
|
|
854
|
-
if ("tool_calls" in m) normalized["tool_calls"] = m["tool_calls"];
|
|
855
|
-
return normalized;
|
|
856
|
-
}),
|
|
857
|
-
{ role: "assistant", content }
|
|
858
|
-
];
|
|
859
|
-
const result2 = {
|
|
942
|
+
const duration = Date.now() - messagesStartTime;
|
|
943
|
+
const content = collectedChunks.join("");
|
|
944
|
+
const result = {
|
|
860
945
|
content,
|
|
861
946
|
model: factoryModel,
|
|
862
947
|
usage: {
|
|
863
948
|
input: 0,
|
|
864
|
-
// Gemini API doesn't always provide token counts
|
|
865
949
|
output: 0
|
|
866
950
|
},
|
|
867
951
|
stop_reason: "stop",
|
|
868
952
|
id: "",
|
|
869
|
-
|
|
870
|
-
|
|
953
|
+
messages: buildResponseMessages(
|
|
954
|
+
inputMessages.map((m) => ({
|
|
955
|
+
role: m["role"],
|
|
956
|
+
content: m["content"] ?? ""
|
|
957
|
+
})),
|
|
958
|
+
content
|
|
959
|
+
)
|
|
871
960
|
};
|
|
872
|
-
const duration = Date.now() - startTime;
|
|
873
961
|
emitExtensionEvent(ctx, {
|
|
874
962
|
event: "gemini:messages",
|
|
875
963
|
subsystem: "extension:gemini",
|
|
876
964
|
duration,
|
|
877
965
|
model: factoryModel,
|
|
878
|
-
usage:
|
|
966
|
+
usage: result.usage,
|
|
879
967
|
request: contents,
|
|
880
968
|
content
|
|
881
969
|
});
|
|
882
|
-
return
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
+
});
|
|
894
992
|
},
|
|
895
993
|
annotations: { description: "Send multi-turn conversation to Gemini API" },
|
|
896
|
-
returnType:
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
+
}
|
|
905
1007
|
}
|
|
906
1008
|
})
|
|
907
1009
|
},
|
|
@@ -949,7 +1051,7 @@ function createGeminiExtension(config) {
|
|
|
949
1051
|
}
|
|
950
1052
|
},
|
|
951
1053
|
annotations: { description: "Generate embedding vector for text" },
|
|
952
|
-
returnType:
|
|
1054
|
+
returnType: structureToTypeValue({ kind: "vector" })
|
|
953
1055
|
},
|
|
954
1056
|
// IR-7: gemini::embed_batch
|
|
955
1057
|
embed_batch: {
|
|
@@ -1010,227 +1112,347 @@ function createGeminiExtension(config) {
|
|
|
1010
1112
|
}
|
|
1011
1113
|
},
|
|
1012
1114
|
annotations: { description: "Generate embedding vectors for multiple texts" },
|
|
1013
|
-
returnType:
|
|
1115
|
+
returnType: structureToTypeValue({ kind: "list", element: { kind: "vector" } })
|
|
1014
1116
|
},
|
|
1015
1117
|
// IR-8: gemini::tool_loop
|
|
1016
1118
|
tool_loop: {
|
|
1017
1119
|
params: [
|
|
1018
1120
|
p.str("prompt"),
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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: [] }
|
|
1026
1133
|
})
|
|
1027
1134
|
],
|
|
1028
|
-
fn:
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
+
}
|
|
1035
1162
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
|
1041
1173
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
if (
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
});
|
|
1062
|
-
}
|
|
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
|
+
};
|
|
1063
1193
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
+
};
|
|
1068
1203
|
});
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
properties[propName] = {
|
|
1086
|
-
type: schemaType,
|
|
1087
|
-
description: prop["description"] ?? ""
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
return {
|
|
1091
|
-
name: def.name,
|
|
1092
|
-
description: def.description,
|
|
1093
|
-
parameters: {
|
|
1094
|
-
type: Type.OBJECT,
|
|
1095
|
-
properties,
|
|
1096
|
-
required: def.input_schema.required
|
|
1097
|
-
}
|
|
1098
|
-
};
|
|
1099
|
-
});
|
|
1100
|
-
},
|
|
1101
|
-
// Call Gemini API
|
|
1102
|
-
callAPI: async (msgs, tools) => {
|
|
1103
|
-
const apiConfig = {
|
|
1104
|
-
...system !== void 0 && { systemInstruction: system },
|
|
1105
|
-
...maxTokens !== void 0 && { maxOutputTokens: maxTokens },
|
|
1106
|
-
...factoryTemperature !== void 0 && {
|
|
1107
|
-
temperature: factoryTemperature
|
|
1108
|
-
},
|
|
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 },
|
|
1109
1220
|
tools: [
|
|
1110
1221
|
{ functionDeclarations: tools }
|
|
1111
1222
|
]
|
|
1112
|
-
};
|
|
1113
|
-
return await client.models.generateContent({
|
|
1114
|
-
model: factoryModel,
|
|
1115
|
-
contents: msgs,
|
|
1116
|
-
config: apiConfig
|
|
1117
|
-
});
|
|
1118
|
-
},
|
|
1119
|
-
// Extract tool calls from Gemini response
|
|
1120
|
-
extractToolCalls: (response2) => {
|
|
1121
|
-
if (!response2 || typeof response2 !== "object" || !("functionCalls" in response2)) {
|
|
1122
|
-
return null;
|
|
1123
1223
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
// Extract the model's content from Gemini response for conversation history
|
|
1138
|
-
formatAssistantMessage: (response2) => {
|
|
1139
|
-
if (!response2 || typeof response2 !== "object" || !("candidates" in response2)) {
|
|
1140
|
-
return null;
|
|
1141
|
-
}
|
|
1142
|
-
const candidates = response2.candidates;
|
|
1143
|
-
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
1144
|
-
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
|
+
]
|
|
1145
1237
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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);
|
|
1149
1245
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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;
|
|
1162
1259
|
return {
|
|
1163
|
-
|
|
1164
|
-
|
|
1260
|
+
id: call.id ?? "",
|
|
1261
|
+
name: call.name ?? "",
|
|
1262
|
+
input: call.args ?? {}
|
|
1165
1263
|
};
|
|
1264
|
+
});
|
|
1265
|
+
},
|
|
1266
|
+
formatAssistantMessage: (response) => {
|
|
1267
|
+
if (!response || typeof response !== "object" || !("candidates" in response)) {
|
|
1268
|
+
return null;
|
|
1166
1269
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
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("");
|
|
1178
1385
|
emitExtensionEvent(ctx, {
|
|
1179
|
-
event:
|
|
1386
|
+
event: "gemini:error",
|
|
1180
1387
|
subsystem: "extension:gemini",
|
|
1181
|
-
|
|
1388
|
+
error: streamError.message,
|
|
1389
|
+
duration: Date.now()
|
|
1182
1390
|
});
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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;
|
|
1188
1411
|
const content = response && typeof response === "object" && "text" in response ? response.text ?? "" : "";
|
|
1189
|
-
const
|
|
1412
|
+
const resolvedResult = {
|
|
1190
1413
|
content,
|
|
1191
1414
|
model: factoryModel,
|
|
1192
|
-
usage:
|
|
1415
|
+
usage: result.totalTokens,
|
|
1193
1416
|
stop_reason: response ? "stop" : "max_turns",
|
|
1194
|
-
turns:
|
|
1195
|
-
messages:
|
|
1196
|
-
...initialMessages,
|
|
1197
|
-
{ role: "user", content: prompt },
|
|
1198
|
-
{ role: "assistant", content }
|
|
1199
|
-
]
|
|
1417
|
+
turns: result.turns,
|
|
1418
|
+
messages: response ? buildResponseMessages(inputMessages, content) : inputMessages
|
|
1200
1419
|
};
|
|
1201
|
-
const
|
|
1420
|
+
const total_duration = Date.now() - startTime;
|
|
1202
1421
|
emitExtensionEvent(ctx, {
|
|
1203
1422
|
event: "gemini:tool_loop",
|
|
1204
1423
|
subsystem: "extension:gemini",
|
|
1205
|
-
turns:
|
|
1206
|
-
total_duration
|
|
1207
|
-
usage:
|
|
1424
|
+
turns: resolvedResult.turns,
|
|
1425
|
+
total_duration,
|
|
1426
|
+
usage: resolvedResult.usage,
|
|
1208
1427
|
request: contents,
|
|
1209
1428
|
content
|
|
1210
1429
|
});
|
|
1211
|
-
return
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
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
|
+
});
|
|
1223
1441
|
},
|
|
1224
1442
|
annotations: { description: "Execute tool-use loop with Gemini API" },
|
|
1225
|
-
returnType:
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
+
}
|
|
1234
1456
|
}
|
|
1235
1457
|
})
|
|
1236
1458
|
},
|
|
@@ -1239,10 +1461,10 @@ function createGeminiExtension(config) {
|
|
|
1239
1461
|
params: [
|
|
1240
1462
|
p.str("prompt"),
|
|
1241
1463
|
p.dict("options", void 0, {}, {
|
|
1242
|
-
schema: { type: {
|
|
1243
|
-
system: { type: {
|
|
1244
|
-
max_tokens: { type: {
|
|
1245
|
-
messages: { type: {
|
|
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: [] }
|
|
1246
1468
|
})
|
|
1247
1469
|
],
|
|
1248
1470
|
fn: async (args, ctx) => {
|
|
@@ -1268,7 +1490,7 @@ function createGeminiExtension(config) {
|
|
|
1268
1490
|
required: jsonSchema.required
|
|
1269
1491
|
};
|
|
1270
1492
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
1271
|
-
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;
|
|
1272
1494
|
const contents = [];
|
|
1273
1495
|
if ("messages" in options && Array.isArray(options["messages"])) {
|
|
1274
1496
|
const prependedMessages = options["messages"];
|
|
@@ -1361,21 +1583,28 @@ function createGeminiExtension(config) {
|
|
|
1361
1583
|
}
|
|
1362
1584
|
},
|
|
1363
1585
|
annotations: { description: "Generate structured output from Gemini API" },
|
|
1364
|
-
returnType:
|
|
1365
|
-
|
|
1586
|
+
returnType: structureToTypeValue({
|
|
1587
|
+
kind: "dict",
|
|
1366
1588
|
fields: {
|
|
1367
|
-
data: { type: {
|
|
1368
|
-
raw: { type: {
|
|
1369
|
-
model: { type: {
|
|
1370
|
-
usage: { type: {
|
|
1371
|
-
stop_reason: { type: {
|
|
1372
|
-
id: { type: {
|
|
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" } }
|
|
1373
1595
|
}
|
|
1374
1596
|
})
|
|
1375
1597
|
}
|
|
1376
1598
|
};
|
|
1377
|
-
|
|
1378
|
-
|
|
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 };
|
|
1379
1608
|
}
|
|
1380
1609
|
|
|
1381
1610
|
// src/index.ts
|