@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/README.md +25 -26
- package/dist/index.d.ts +4 -3
- package/dist/index.js +663 -366
- package/package.json +5 -5
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
|
-
|
|
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.
|
|
104
|
-
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}`);
|
|
105
110
|
}
|
|
106
|
-
if (rillType.
|
|
111
|
+
if (rillType.kind === "any") {
|
|
107
112
|
return {};
|
|
108
113
|
}
|
|
109
|
-
if (rillType.
|
|
114
|
+
if (rillType.kind === "list") {
|
|
115
|
+
const listType = rillType;
|
|
110
116
|
const property = { type: "array" };
|
|
111
|
-
if (
|
|
112
|
-
property.items = buildPropertyFromStructuralType(
|
|
117
|
+
if (listType.element !== void 0) {
|
|
118
|
+
property.items = buildPropertyFromStructuralType(listType.element);
|
|
113
119
|
}
|
|
114
120
|
return property;
|
|
115
121
|
}
|
|
116
|
-
if (rillType.
|
|
122
|
+
if (rillType.kind === "dict") {
|
|
117
123
|
return { type: "object" };
|
|
118
124
|
}
|
|
119
|
-
return { type: mapRillType(rillType.
|
|
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.
|
|
130
|
+
if (type.kind === "closure") {
|
|
125
131
|
const closureParams = type.params ?? [];
|
|
126
132
|
for (let i = 0; i < closureParams.length; i++) {
|
|
127
|
-
const
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
352
|
-
params: params.map((p2) =>
|
|
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
|
};
|
|
@@ -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
|
-
* @
|
|
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
|
|
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 ? {
|
|
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: {
|
|
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
|
|
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
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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
|
-
|
|
705
|
-
|
|
764
|
+
}
|
|
765
|
+
const resolve = async () => {
|
|
766
|
+
if (streamError) {
|
|
767
|
+
throw streamError;
|
|
706
768
|
}
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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:
|
|
793
|
+
usage: result.usage,
|
|
737
794
|
request: contents,
|
|
738
795
|
content
|
|
739
796
|
});
|
|
740
|
-
return
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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: {
|
|
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
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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
|
-
"
|
|
863
|
+
"message missing required 'role' field"
|
|
771
864
|
);
|
|
772
865
|
}
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
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") {
|
|
779
872
|
throw new RuntimeError6(
|
|
780
873
|
"RILL-R004",
|
|
781
|
-
|
|
874
|
+
`${role} message requires 'content'`
|
|
782
875
|
);
|
|
783
876
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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 (
|
|
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: "
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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
|
-
|
|
824
|
-
|
|
937
|
+
}
|
|
938
|
+
const resolve = async () => {
|
|
939
|
+
if (streamError) {
|
|
940
|
+
throw streamError;
|
|
825
941
|
}
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
852
|
-
|
|
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:
|
|
966
|
+
usage: result.usage,
|
|
861
967
|
request: contents,
|
|
862
968
|
content
|
|
863
969
|
});
|
|
864
|
-
return
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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: {
|
|
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[
|
|
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: {
|
|
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[
|
|
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: {
|
|
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
|
-
|
|
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:
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
|
|
1129
|
-
|
|
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
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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:
|
|
1386
|
+
event: "gemini:error",
|
|
1145
1387
|
subsystem: "extension:gemini",
|
|
1146
|
-
|
|
1388
|
+
error: streamError.message,
|
|
1389
|
+
duration: Date.now()
|
|
1147
1390
|
});
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
|
1412
|
+
const resolvedResult = {
|
|
1155
1413
|
content,
|
|
1156
1414
|
model: factoryModel,
|
|
1157
|
-
usage:
|
|
1415
|
+
usage: result.totalTokens,
|
|
1158
1416
|
stop_reason: response ? "stop" : "max_turns",
|
|
1159
|
-
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
|
|
1420
|
+
const total_duration = Date.now() - startTime;
|
|
1167
1421
|
emitExtensionEvent(ctx, {
|
|
1168
1422
|
event: "gemini:tool_loop",
|
|
1169
1423
|
subsystem: "extension:gemini",
|
|
1170
|
-
turns:
|
|
1171
|
-
total_duration
|
|
1172
|
-
usage:
|
|
1424
|
+
turns: resolvedResult.turns,
|
|
1425
|
+
total_duration,
|
|
1426
|
+
usage: resolvedResult.usage,
|
|
1173
1427
|
request: contents,
|
|
1174
1428
|
content
|
|
1175
1429
|
});
|
|
1176
|
-
return
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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: {
|
|
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[
|
|
1202
|
-
const options = args[
|
|
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: {
|
|
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
|
-
|
|
1318
|
-
|
|
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
|
|
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
|
};
|