@rcrsr/rill-ext-gemini 0.8.6 → 0.11.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 +7 -123
- package/dist/index.d.ts +2 -6
- package/dist/index.js +600 -125
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rcrsr/rill-ext-gemini
|
|
2
2
|
|
|
3
|
-
[rill](https://rill.run) extension for [Google Gemini](https://ai.google.dev/docs) API integration. Provides `message`, `messages`, `embed`, `embed_batch`, and `
|
|
3
|
+
[rill](https://rill.run) extension for [Google Gemini](https://ai.google.dev/docs) API integration. Provides `message`, `messages`, `embed`, `embed_batch`, `tool_loop`, and `generate` host functions.
|
|
4
4
|
|
|
5
5
|
> **Experimental.** Breaking changes will occur before stabilization.
|
|
6
6
|
|
|
@@ -36,131 +36,15 @@ const result = await execute(parse(script), ctx);
|
|
|
36
36
|
dispose?.();
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
All functions return a dict with `content`, `model`, `usage`, `stop_reason`, `id`, and `messages`.
|
|
42
|
-
|
|
43
|
-
### gemini::message(text, options?)
|
|
44
|
-
|
|
45
|
-
Send a single message to Gemini.
|
|
46
|
-
|
|
47
|
-
```rill
|
|
48
|
-
gemini::message("Analyze this code for security issues") => $response
|
|
49
|
-
$response.content -> log
|
|
50
|
-
$response.usage.output -> log
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### gemini::messages(messages, options?)
|
|
54
|
-
|
|
55
|
-
Send a multi-turn conversation.
|
|
56
|
-
|
|
57
|
-
```rill
|
|
58
|
-
gemini::messages([
|
|
59
|
-
[role: "user", content: "What is rill?"],
|
|
60
|
-
[role: "assistant", content: "A scripting language for AI agents."],
|
|
61
|
-
[role: "user", content: "Show me an example."]
|
|
62
|
-
]) => $response
|
|
63
|
-
$response.content -> log
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### gemini::embed(text)
|
|
67
|
-
|
|
68
|
-
Generate an embedding vector for text. Requires `embed_model` in config.
|
|
69
|
-
|
|
70
|
-
```rill
|
|
71
|
-
gemini::embed("Hello world") => $vector
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### gemini::embed_batch(texts)
|
|
75
|
-
|
|
76
|
-
Generate embedding vectors for multiple texts in a single API call.
|
|
77
|
-
|
|
78
|
-
```rill
|
|
79
|
-
gemini::embed_batch(["Hello", "World"]) => $vectors
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### gemini::tool_loop(prompt, options)
|
|
83
|
-
|
|
84
|
-
Execute a tool-use loop where the model calls rill functions iteratively.
|
|
85
|
-
|
|
86
|
-
```rill
|
|
87
|
-
gemini::tool_loop("Find the weather", [tools: $my_tools]) => $result
|
|
88
|
-
$result.content -> log
|
|
89
|
-
$result.turns -> log
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Configuration
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
const ext = createGeminiExtension({
|
|
96
|
-
api_key: process.env.GOOGLE_API_KEY!,
|
|
97
|
-
model: 'gemini-2.0-flash',
|
|
98
|
-
temperature: 0.7,
|
|
99
|
-
max_tokens: 8192,
|
|
100
|
-
system: 'You are a helpful assistant.',
|
|
101
|
-
embed_model: 'text-embedding-004',
|
|
102
|
-
});
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
| Option | Type | Default | Description |
|
|
106
|
-
|--------|------|---------|-------------|
|
|
107
|
-
| `api_key` | string | required | Google API key |
|
|
108
|
-
| `model` | string | required | Model identifier |
|
|
109
|
-
| `temperature` | number | undefined | Temperature (0.0-2.0) |
|
|
110
|
-
| `base_url` | string | undefined | Custom API endpoint URL |
|
|
111
|
-
| `max_tokens` | number | `8192` | Max tokens in response |
|
|
112
|
-
| `max_retries` | number | undefined | Max retry attempts |
|
|
113
|
-
| `timeout` | number | undefined | Request timeout in ms |
|
|
114
|
-
| `system` | string | undefined | Default system instruction |
|
|
115
|
-
| `embed_model` | string | undefined | Embedding model identifier |
|
|
116
|
-
|
|
117
|
-
## Result Shape
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
interface GeminiResult {
|
|
121
|
-
content: string; // response text
|
|
122
|
-
model: string; // model used
|
|
123
|
-
usage: {
|
|
124
|
-
input: number; // prompt tokens
|
|
125
|
-
output: number; // completion tokens
|
|
126
|
-
};
|
|
127
|
-
stop_reason: string; // finish reason
|
|
128
|
-
id: string; // request ID
|
|
129
|
-
messages: Array<{ // full conversation history
|
|
130
|
-
role: string;
|
|
131
|
-
content: string;
|
|
132
|
-
}>;
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Lifecycle
|
|
137
|
-
|
|
138
|
-
Call `dispose()` on the extension to cancel pending requests:
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
const ext = createGeminiExtension({ ... });
|
|
142
|
-
// ... use extension ...
|
|
143
|
-
await ext.dispose?.();
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Test Host
|
|
147
|
-
|
|
148
|
-
A runnable example at `examples/test-host.ts` wires up the extension with the rill runtime:
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
pnpm exec tsx examples/test-host.ts
|
|
152
|
-
pnpm exec tsx examples/test-host.ts -e 'gemini::message("Tell me a joke") -> log'
|
|
153
|
-
pnpm exec tsx examples/test-host.ts script.rill
|
|
154
|
-
```
|
|
39
|
+
## Documentation
|
|
155
40
|
|
|
156
|
-
|
|
41
|
+
See [full documentation](docs/extension-llm-gemini.md) for configuration, functions, error handling, events, and examples.
|
|
157
42
|
|
|
158
|
-
##
|
|
43
|
+
## Related
|
|
159
44
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
| [Host API Reference](https://github.com/rcrsr/rill/blob/main/docs/ref-host-api.md) | Runtime context and host functions |
|
|
45
|
+
- [rill](https://github.com/rcrsr/rill) — Core language runtime
|
|
46
|
+
- [Extensions Guide](https://github.com/rcrsr/rill/blob/main/docs/integration-extensions.md) — Extension contract and patterns
|
|
47
|
+
- [Host API Reference](https://github.com/rcrsr/rill/blob/main/docs/ref-host-api.md) — Runtime context and host functions
|
|
164
48
|
|
|
165
49
|
## License
|
|
166
50
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
-
import { ExtensionResult } from '@rcrsr/rill';
|
|
3
|
+
import { ExtensionConfigSchema, ExtensionResult } from '@rcrsr/rill';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Base configuration for LLM extensions
|
|
@@ -73,12 +73,8 @@ export type GeminiExtensionConfig = LLMProviderConfig;
|
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
75
|
export declare function createGeminiExtension(config: GeminiExtensionConfig): ExtensionResult;
|
|
76
|
-
/**
|
|
77
|
-
* @rcrsr/rill-ext-gemini
|
|
78
|
-
*
|
|
79
|
-
* Extension for Google Gemini API integration with rill scripts.
|
|
80
|
-
*/
|
|
81
76
|
export declare const VERSION = "0.0.1";
|
|
77
|
+
export declare const configSchema: ExtensionConfigSchema;
|
|
82
78
|
|
|
83
79
|
export {
|
|
84
80
|
LLMProviderConfig as LLMExtensionConfig,
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
Type
|
|
5
5
|
} from "@google/genai";
|
|
6
6
|
import {
|
|
7
|
-
RuntimeError as
|
|
7
|
+
RuntimeError as RuntimeError6,
|
|
8
8
|
emitExtensionEvent,
|
|
9
9
|
createVector,
|
|
10
10
|
isVector,
|
|
11
|
-
|
|
11
|
+
isDict as isDict2
|
|
12
12
|
} from "@rcrsr/rill";
|
|
13
13
|
|
|
14
14
|
// ../../shared/ext-llm/dist/validation.js
|
|
@@ -79,29 +79,152 @@ function mapProviderError(providerName, error, detect) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// ../../shared/ext-llm/dist/tool-loop.js
|
|
82
|
-
import { isCallable, isDict, RuntimeError as
|
|
82
|
+
import { invokeCallable, isCallable, isDict, isRuntimeCallable, RuntimeError as RuntimeError4 } from "@rcrsr/rill";
|
|
83
|
+
|
|
84
|
+
// ../../shared/ext-llm/dist/schema.js
|
|
85
|
+
import { RuntimeError as RuntimeError3 } from "@rcrsr/rill";
|
|
86
|
+
var RILL_TYPE_MAP = {
|
|
87
|
+
string: "string",
|
|
88
|
+
number: "number",
|
|
89
|
+
bool: "boolean",
|
|
90
|
+
list: "array",
|
|
91
|
+
dict: "object",
|
|
92
|
+
vector: "object",
|
|
93
|
+
shape: "object"
|
|
94
|
+
};
|
|
95
|
+
function mapRillType(rillType) {
|
|
96
|
+
const jsonType = RILL_TYPE_MAP[rillType];
|
|
97
|
+
if (jsonType === void 0) {
|
|
98
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${rillType}`);
|
|
99
|
+
}
|
|
100
|
+
return jsonType;
|
|
101
|
+
}
|
|
102
|
+
function buildPropertyFromStructuralType(rillType) {
|
|
103
|
+
if (rillType.type === "closure" || rillType.type === "tuple") {
|
|
104
|
+
throw new RuntimeError3("RILL-R004", `unsupported type for JSON Schema: ${rillType.type}`);
|
|
105
|
+
}
|
|
106
|
+
if (rillType.type === "any") {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
if (rillType.type === "list") {
|
|
110
|
+
const property = { type: "array" };
|
|
111
|
+
if (rillType.element !== void 0) {
|
|
112
|
+
property.items = buildPropertyFromStructuralType(rillType.element);
|
|
113
|
+
}
|
|
114
|
+
return property;
|
|
115
|
+
}
|
|
116
|
+
if (rillType.type === "dict") {
|
|
117
|
+
return { type: "object" };
|
|
118
|
+
}
|
|
119
|
+
return { type: mapRillType(rillType.type) };
|
|
120
|
+
}
|
|
121
|
+
function buildJsonSchemaFromStructuralType(type, params) {
|
|
122
|
+
const properties = {};
|
|
123
|
+
const required = [];
|
|
124
|
+
if (type.type === "closure") {
|
|
125
|
+
const closureParams = type.params ?? [];
|
|
126
|
+
for (let i = 0; i < closureParams.length; i++) {
|
|
127
|
+
const [paramName, paramType] = closureParams[i];
|
|
128
|
+
const rillParam = params?.[i];
|
|
129
|
+
const property = buildPropertyFromStructuralType(paramType);
|
|
130
|
+
const description = rillParam?.annotations["description"];
|
|
131
|
+
if (typeof description === "string") {
|
|
132
|
+
property.description = description;
|
|
133
|
+
}
|
|
134
|
+
const enumAnnotation = rillParam?.annotations["enum"];
|
|
135
|
+
if (Array.isArray(enumAnnotation)) {
|
|
136
|
+
property.enum = enumAnnotation;
|
|
137
|
+
}
|
|
138
|
+
properties[paramName] = property;
|
|
139
|
+
if (rillParam === void 0 || rillParam.defaultValue === void 0) {
|
|
140
|
+
required.push(paramName);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
145
|
+
}
|
|
146
|
+
function buildJsonSchema(rillSchema) {
|
|
147
|
+
const properties = {};
|
|
148
|
+
const required = [];
|
|
149
|
+
for (const [key, value] of Object.entries(rillSchema)) {
|
|
150
|
+
if (typeof value === "string") {
|
|
151
|
+
properties[key] = buildProperty(value);
|
|
152
|
+
} else if (typeof value === "object" && value !== null) {
|
|
153
|
+
properties[key] = buildProperty(value);
|
|
154
|
+
} else {
|
|
155
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${String(value)}`);
|
|
156
|
+
}
|
|
157
|
+
required.push(key);
|
|
158
|
+
}
|
|
159
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
160
|
+
}
|
|
161
|
+
function buildProperty(descriptor) {
|
|
162
|
+
if (typeof descriptor === "string") {
|
|
163
|
+
const jsonType2 = mapRillType(descriptor);
|
|
164
|
+
return { type: jsonType2 };
|
|
165
|
+
}
|
|
166
|
+
const rillType = descriptor["type"];
|
|
167
|
+
if (typeof rillType !== "string") {
|
|
168
|
+
throw new RuntimeError3("RILL-R004", `unsupported type: ${String(rillType)}`);
|
|
169
|
+
}
|
|
170
|
+
const jsonType = mapRillType(rillType);
|
|
171
|
+
const property = { type: jsonType };
|
|
172
|
+
const description = descriptor["description"];
|
|
173
|
+
if (typeof description === "string") {
|
|
174
|
+
property.description = description;
|
|
175
|
+
}
|
|
176
|
+
if ("enum" in descriptor) {
|
|
177
|
+
if (rillType !== "string") {
|
|
178
|
+
throw new RuntimeError3("RILL-R004", "enum is only valid for string type");
|
|
179
|
+
}
|
|
180
|
+
const enumValues = descriptor["enum"];
|
|
181
|
+
if (Array.isArray(enumValues)) {
|
|
182
|
+
property.enum = enumValues;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (rillType === "list" && "items" in descriptor) {
|
|
186
|
+
const items = descriptor["items"];
|
|
187
|
+
if (typeof items === "string") {
|
|
188
|
+
property.items = buildProperty(items);
|
|
189
|
+
} else if (typeof items === "object" && items !== null) {
|
|
190
|
+
property.items = buildProperty(items);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (rillType === "dict" && "properties" in descriptor) {
|
|
194
|
+
const nestedProps = descriptor["properties"];
|
|
195
|
+
if (typeof nestedProps === "object" && nestedProps !== null) {
|
|
196
|
+
const subSchema = buildJsonSchema(nestedProps);
|
|
197
|
+
property.properties = subSchema.properties;
|
|
198
|
+
property.required = subSchema.required;
|
|
199
|
+
property.additionalProperties = false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return property;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ../../shared/ext-llm/dist/tool-loop.js
|
|
83
206
|
async function executeToolCall(toolName, toolInput, tools, context) {
|
|
84
207
|
if (!isDict(tools)) {
|
|
85
|
-
throw new
|
|
208
|
+
throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
|
|
86
209
|
}
|
|
87
210
|
const toolsDict = tools;
|
|
88
211
|
const toolFn = toolsDict[toolName];
|
|
89
212
|
if (toolFn === void 0 || toolFn === null) {
|
|
90
|
-
throw new
|
|
213
|
+
throw new RuntimeError4("RILL-R004", `Unknown tool: ${toolName}`);
|
|
91
214
|
}
|
|
92
215
|
if (!isCallable(toolFn)) {
|
|
93
|
-
throw new
|
|
216
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be callable`);
|
|
94
217
|
}
|
|
95
218
|
if (typeof toolInput !== "object" || toolInput === null) {
|
|
96
|
-
throw new
|
|
219
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: input must be an object`);
|
|
97
220
|
}
|
|
98
221
|
const callable = toolFn;
|
|
99
|
-
if (callable.kind !== "runtime" && callable.kind !== "application") {
|
|
100
|
-
throw new
|
|
222
|
+
if (callable.kind !== "runtime" && callable.kind !== "application" && callable.kind !== "script") {
|
|
223
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: tool must be application, runtime, or script callable`);
|
|
101
224
|
}
|
|
102
225
|
try {
|
|
103
226
|
let args;
|
|
104
|
-
if (callable.kind === "application" && callable.params) {
|
|
227
|
+
if ((callable.kind === "application" || callable.kind === "script") && callable.params && callable.params.length > 0) {
|
|
105
228
|
const params = callable.params;
|
|
106
229
|
const inputDict = toolInput;
|
|
107
230
|
args = params.map((param) => {
|
|
@@ -111,6 +234,12 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
111
234
|
} else {
|
|
112
235
|
args = [toolInput];
|
|
113
236
|
}
|
|
237
|
+
if (callable.kind === "script") {
|
|
238
|
+
if (!context) {
|
|
239
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: script callable requires a runtime context`);
|
|
240
|
+
}
|
|
241
|
+
return await invokeCallable(callable, args, context);
|
|
242
|
+
}
|
|
114
243
|
const ctx = context ?? {
|
|
115
244
|
parent: void 0,
|
|
116
245
|
variables: /* @__PURE__ */ new Map(),
|
|
@@ -119,77 +248,122 @@ async function executeToolCall(toolName, toolInput, tools, context) {
|
|
|
119
248
|
const result = callable.fn(args, ctx);
|
|
120
249
|
return result instanceof Promise ? await result : result;
|
|
121
250
|
} catch (error) {
|
|
122
|
-
if (error instanceof
|
|
251
|
+
if (error instanceof RuntimeError4) {
|
|
123
252
|
throw error;
|
|
124
253
|
}
|
|
125
254
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
126
|
-
throw new
|
|
255
|
+
throw new RuntimeError4("RILL-R004", `Invalid tool input for ${toolName}: ${message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function sanitizeToolName(name) {
|
|
259
|
+
const match = name.match(/^[a-zA-Z0-9_-]*/);
|
|
260
|
+
const sanitized = match ? match[0] : "";
|
|
261
|
+
return sanitized.length > 0 ? sanitized : name;
|
|
262
|
+
}
|
|
263
|
+
function patchResponseToolCallNames(response, nameMap) {
|
|
264
|
+
if (!nameMap.size || !response || typeof response !== "object")
|
|
265
|
+
return;
|
|
266
|
+
const resp = response;
|
|
267
|
+
if (Array.isArray(resp["choices"])) {
|
|
268
|
+
for (const choice of resp["choices"]) {
|
|
269
|
+
const msg = choice?.["message"];
|
|
270
|
+
const tcs = msg?.["tool_calls"];
|
|
271
|
+
if (Array.isArray(tcs)) {
|
|
272
|
+
for (const tc of tcs) {
|
|
273
|
+
const fn = tc?.["function"];
|
|
274
|
+
if (fn && typeof fn["name"] === "string") {
|
|
275
|
+
const orig = fn["name"];
|
|
276
|
+
const san = nameMap.get(orig);
|
|
277
|
+
if (san !== void 0)
|
|
278
|
+
fn["name"] = san;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (Array.isArray(resp["content"])) {
|
|
285
|
+
for (const block of resp["content"]) {
|
|
286
|
+
const b = block;
|
|
287
|
+
if (b?.["type"] === "tool_use" && typeof b?.["name"] === "string") {
|
|
288
|
+
const orig = b["name"];
|
|
289
|
+
const san = nameMap.get(orig);
|
|
290
|
+
if (san !== void 0)
|
|
291
|
+
b["name"] = san;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (Array.isArray(resp["functionCalls"])) {
|
|
296
|
+
for (const fc of resp["functionCalls"]) {
|
|
297
|
+
const f = fc;
|
|
298
|
+
if (typeof f?.["name"] === "string") {
|
|
299
|
+
const orig = f["name"];
|
|
300
|
+
const san = nameMap.get(orig);
|
|
301
|
+
if (san !== void 0)
|
|
302
|
+
f["name"] = san;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (Array.isArray(resp["candidates"])) {
|
|
307
|
+
for (const cand of resp["candidates"]) {
|
|
308
|
+
const content = cand?.["content"];
|
|
309
|
+
const parts = content?.["parts"];
|
|
310
|
+
if (Array.isArray(parts)) {
|
|
311
|
+
for (const part of parts) {
|
|
312
|
+
const fc = part?.["functionCall"];
|
|
313
|
+
if (fc && typeof fc["name"] === "string") {
|
|
314
|
+
const orig = fc["name"];
|
|
315
|
+
const san = nameMap.get(orig);
|
|
316
|
+
if (san !== void 0)
|
|
317
|
+
fc["name"] = san;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
127
322
|
}
|
|
128
323
|
}
|
|
129
324
|
async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent, maxTurns = 10, context) {
|
|
130
325
|
if (tools === void 0) {
|
|
131
|
-
throw new
|
|
326
|
+
throw new RuntimeError4("RILL-R004", "tools parameter is required");
|
|
132
327
|
}
|
|
133
328
|
if (!isDict(tools)) {
|
|
134
|
-
throw new
|
|
329
|
+
throw new RuntimeError4("RILL-R004", "tool_loop: tools must be a dict of name \u2192 callable");
|
|
135
330
|
}
|
|
136
331
|
const toolsDict = tools;
|
|
137
332
|
const toolDescriptors = Object.entries(toolsDict).map(([name, fn]) => {
|
|
138
333
|
const fnValue = fn;
|
|
334
|
+
if (isRuntimeCallable(fnValue)) {
|
|
335
|
+
throw new RuntimeError4("RILL-R004", `tool_loop: builtin "${name}" cannot be used as a tool \u2014 wrap in a closure`);
|
|
336
|
+
}
|
|
139
337
|
if (!isCallable(fnValue)) {
|
|
140
|
-
throw new
|
|
338
|
+
throw new RuntimeError4("RILL-R004", `tool_loop: tool "${name}" is not a callable`);
|
|
141
339
|
}
|
|
142
340
|
const callable = fnValue;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
jsonSchemaType = "object";
|
|
165
|
-
break;
|
|
166
|
-
case null:
|
|
167
|
-
jsonSchemaType = "string";
|
|
168
|
-
break;
|
|
169
|
-
default:
|
|
170
|
-
jsonSchemaType = "string";
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
const property = {
|
|
174
|
-
type: jsonSchemaType
|
|
175
|
-
};
|
|
176
|
-
if (param.description) {
|
|
177
|
-
property["description"] = param.description;
|
|
178
|
-
}
|
|
179
|
-
properties[param.name] = property;
|
|
180
|
-
if (param.defaultValue === null) {
|
|
181
|
-
required.push(param.name);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
341
|
+
let description;
|
|
342
|
+
if (callable.kind === "script") {
|
|
343
|
+
description = callable.annotations["description"] ?? "";
|
|
344
|
+
} else {
|
|
345
|
+
description = callable.description ?? "";
|
|
346
|
+
}
|
|
347
|
+
let inputSchema;
|
|
348
|
+
const params = callable.kind === "application" ? callable.params ?? [] : callable.kind === "script" ? callable.params : [];
|
|
349
|
+
if (params.length > 0) {
|
|
350
|
+
const closureType = {
|
|
351
|
+
type: "closure",
|
|
352
|
+
params: params.map((p2) => [p2.name, p2.type ?? { type: "any" }])
|
|
353
|
+
};
|
|
354
|
+
const builtSchema = buildJsonSchemaFromStructuralType(closureType, [...params]);
|
|
355
|
+
inputSchema = {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: builtSchema.properties,
|
|
358
|
+
required: builtSchema.required
|
|
359
|
+
};
|
|
360
|
+
} else {
|
|
361
|
+
inputSchema = { type: "object", properties: {}, required: [] };
|
|
184
362
|
}
|
|
185
363
|
return {
|
|
186
364
|
name,
|
|
187
365
|
description,
|
|
188
|
-
input_schema:
|
|
189
|
-
type: "object",
|
|
190
|
-
properties,
|
|
191
|
-
required
|
|
192
|
-
}
|
|
366
|
+
input_schema: inputSchema
|
|
193
367
|
};
|
|
194
368
|
});
|
|
195
369
|
const providerTools = callbacks.buildTools(toolDescriptors);
|
|
@@ -206,7 +380,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
206
380
|
response = await callbacks.callAPI(currentMessages, providerTools);
|
|
207
381
|
} catch (error) {
|
|
208
382
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
209
|
-
throw new
|
|
383
|
+
throw new RuntimeError4("RILL-R004", `Provider API error: ${message}`, void 0, { cause: error });
|
|
210
384
|
}
|
|
211
385
|
if (typeof response === "object" && response !== null && "usage" in response) {
|
|
212
386
|
const usage = response["usage"];
|
|
@@ -218,7 +392,17 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
218
392
|
totalOutputTokens += outputTokens;
|
|
219
393
|
}
|
|
220
394
|
}
|
|
221
|
-
const
|
|
395
|
+
const rawToolCalls = callbacks.extractToolCalls(response);
|
|
396
|
+
const nameMap = /* @__PURE__ */ new Map();
|
|
397
|
+
const toolCalls = rawToolCalls?.map((tc) => {
|
|
398
|
+
const sanitized = sanitizeToolName(tc.name);
|
|
399
|
+
if (sanitized !== tc.name)
|
|
400
|
+
nameMap.set(tc.name, sanitized);
|
|
401
|
+
return sanitized !== tc.name ? { ...tc, name: sanitized } : tc;
|
|
402
|
+
}) ?? null;
|
|
403
|
+
if (nameMap.size > 0) {
|
|
404
|
+
patchResponseToolCallNames(response, nameMap);
|
|
405
|
+
}
|
|
222
406
|
if (toolCalls === null || toolCalls.length === 0) {
|
|
223
407
|
return {
|
|
224
408
|
response,
|
|
@@ -243,7 +427,7 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
243
427
|
const duration = Date.now() - toolStartTime;
|
|
244
428
|
consecutiveErrors++;
|
|
245
429
|
let originalError;
|
|
246
|
-
if (error instanceof
|
|
430
|
+
if (error instanceof RuntimeError4) {
|
|
247
431
|
const prefix = `Invalid tool input for ${name}: `;
|
|
248
432
|
if (error.message.startsWith(prefix)) {
|
|
249
433
|
originalError = error.message.slice(prefix.length);
|
|
@@ -268,12 +452,20 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
268
452
|
duration
|
|
269
453
|
});
|
|
270
454
|
if (consecutiveErrors >= maxErrors) {
|
|
271
|
-
throw new
|
|
455
|
+
throw new RuntimeError4("RILL-R004", `Tool execution failed: ${maxErrors} consecutive errors (last: ${name}: ${originalError})`);
|
|
272
456
|
}
|
|
273
457
|
}
|
|
274
458
|
}
|
|
459
|
+
const assistantMessage = callbacks.formatAssistantMessage(response);
|
|
460
|
+
if (assistantMessage != null) {
|
|
461
|
+
currentMessages.push(assistantMessage);
|
|
462
|
+
}
|
|
275
463
|
const toolResultMessage = callbacks.formatToolResult(toolResults);
|
|
276
|
-
|
|
464
|
+
if (Array.isArray(toolResultMessage)) {
|
|
465
|
+
currentMessages.push(...toolResultMessage);
|
|
466
|
+
} else {
|
|
467
|
+
currentMessages.push(toolResultMessage);
|
|
468
|
+
}
|
|
277
469
|
}
|
|
278
470
|
return {
|
|
279
471
|
response: null,
|
|
@@ -283,6 +475,126 @@ async function executeToolLoop(messages, tools, maxErrors, callbacks, emitEvent,
|
|
|
283
475
|
};
|
|
284
476
|
}
|
|
285
477
|
|
|
478
|
+
// ../../shared/ext-param/dist/param.js
|
|
479
|
+
import { RuntimeError as RuntimeError5 } from "@rcrsr/rill";
|
|
480
|
+
function validateParamName(name) {
|
|
481
|
+
if (name === "") {
|
|
482
|
+
throw new RuntimeError5("RILL-R001", "param name must not be empty");
|
|
483
|
+
}
|
|
484
|
+
if (/\s/.test(name)) {
|
|
485
|
+
throw new RuntimeError5("RILL-R001", "param name must be a valid identifier");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function buildAnnotations(desc) {
|
|
489
|
+
if (desc !== void 0) {
|
|
490
|
+
return { description: desc };
|
|
491
|
+
}
|
|
492
|
+
return {};
|
|
493
|
+
}
|
|
494
|
+
var p = {
|
|
495
|
+
/**
|
|
496
|
+
* IR-1: Creates a string parameter descriptor.
|
|
497
|
+
*
|
|
498
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
499
|
+
* @param desc - Optional description
|
|
500
|
+
* @returns RillParam with type 'string'
|
|
501
|
+
*/
|
|
502
|
+
str(name, desc) {
|
|
503
|
+
validateParamName(name);
|
|
504
|
+
return {
|
|
505
|
+
name,
|
|
506
|
+
type: { type: "string" },
|
|
507
|
+
defaultValue: void 0,
|
|
508
|
+
annotations: buildAnnotations(desc)
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
/**
|
|
512
|
+
* IR-2: Creates a number parameter descriptor.
|
|
513
|
+
*
|
|
514
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
515
|
+
* @param desc - Optional description
|
|
516
|
+
* @param def - Optional default value
|
|
517
|
+
* @returns RillParam with type 'number'
|
|
518
|
+
*/
|
|
519
|
+
num(name, desc, def) {
|
|
520
|
+
validateParamName(name);
|
|
521
|
+
return {
|
|
522
|
+
name,
|
|
523
|
+
type: { type: "number" },
|
|
524
|
+
defaultValue: def,
|
|
525
|
+
annotations: buildAnnotations(desc)
|
|
526
|
+
};
|
|
527
|
+
},
|
|
528
|
+
/**
|
|
529
|
+
* IR-3: Creates a boolean parameter descriptor.
|
|
530
|
+
*
|
|
531
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
532
|
+
* @param desc - Optional description
|
|
533
|
+
* @param def - Optional default value
|
|
534
|
+
* @returns RillParam with type 'bool'
|
|
535
|
+
*/
|
|
536
|
+
bool(name, desc, def) {
|
|
537
|
+
validateParamName(name);
|
|
538
|
+
return {
|
|
539
|
+
name,
|
|
540
|
+
type: { type: "bool" },
|
|
541
|
+
defaultValue: def,
|
|
542
|
+
annotations: buildAnnotations(desc)
|
|
543
|
+
};
|
|
544
|
+
},
|
|
545
|
+
/**
|
|
546
|
+
* IR-4: Creates a dict parameter descriptor.
|
|
547
|
+
*
|
|
548
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
549
|
+
* @param desc - Optional description
|
|
550
|
+
* @param def - Optional default value
|
|
551
|
+
* @returns RillParam with type 'dict'
|
|
552
|
+
*/
|
|
553
|
+
dict(name, desc, def) {
|
|
554
|
+
validateParamName(name);
|
|
555
|
+
return {
|
|
556
|
+
name,
|
|
557
|
+
type: { type: "dict" },
|
|
558
|
+
defaultValue: def,
|
|
559
|
+
annotations: buildAnnotations(desc)
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
/**
|
|
563
|
+
* IR-5: Creates a list parameter descriptor.
|
|
564
|
+
*
|
|
565
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
566
|
+
* @param itemType - Optional element type; omitted when not provided
|
|
567
|
+
* @param desc - Optional description
|
|
568
|
+
* @returns RillParam with type 'list' (with element if itemType provided)
|
|
569
|
+
*/
|
|
570
|
+
list(name, itemType, desc) {
|
|
571
|
+
validateParamName(name);
|
|
572
|
+
const type = itemType !== void 0 ? { type: "list", element: itemType } : { type: "list" };
|
|
573
|
+
return {
|
|
574
|
+
name,
|
|
575
|
+
type,
|
|
576
|
+
defaultValue: void 0,
|
|
577
|
+
annotations: buildAnnotations(desc)
|
|
578
|
+
};
|
|
579
|
+
},
|
|
580
|
+
/**
|
|
581
|
+
* IR-6: Creates a callable parameter descriptor.
|
|
582
|
+
*
|
|
583
|
+
* @param name - Parameter name (must be a valid identifier)
|
|
584
|
+
* @param desc - Optional description
|
|
585
|
+
* @returns RillParam with type 'closure'
|
|
586
|
+
*/
|
|
587
|
+
callable(name, desc) {
|
|
588
|
+
validateParamName(name);
|
|
589
|
+
return {
|
|
590
|
+
name,
|
|
591
|
+
type: { type: "closure" },
|
|
592
|
+
defaultValue: void 0,
|
|
593
|
+
annotations: buildAnnotations(desc)
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
286
598
|
// src/factory.ts
|
|
287
599
|
var DEFAULT_MAX_TOKENS = 8192;
|
|
288
600
|
var detectGeminiError = (error) => {
|
|
@@ -301,6 +613,35 @@ var detectGeminiError = (error) => {
|
|
|
301
613
|
}
|
|
302
614
|
return null;
|
|
303
615
|
};
|
|
616
|
+
function toGeminiSchema(prop) {
|
|
617
|
+
let schemaType = Type.STRING;
|
|
618
|
+
if (prop.type === "number") schemaType = Type.NUMBER;
|
|
619
|
+
if (prop.type === "boolean") schemaType = Type.BOOLEAN;
|
|
620
|
+
if (prop.type === "integer") schemaType = Type.INTEGER;
|
|
621
|
+
if (prop.type === "array") schemaType = Type.ARRAY;
|
|
622
|
+
if (prop.type === "object") schemaType = Type.OBJECT;
|
|
623
|
+
const schema = { type: schemaType };
|
|
624
|
+
if (prop.description !== void 0) {
|
|
625
|
+
schema.description = prop.description;
|
|
626
|
+
}
|
|
627
|
+
if (prop.enum !== void 0) {
|
|
628
|
+
schema.enum = prop.enum;
|
|
629
|
+
}
|
|
630
|
+
if (prop.type === "array" && prop.items !== void 0) {
|
|
631
|
+
schema.items = toGeminiSchema(prop.items);
|
|
632
|
+
}
|
|
633
|
+
if (prop.type === "object" && prop.properties !== void 0) {
|
|
634
|
+
const nestedProperties = {};
|
|
635
|
+
for (const [key, nestedProp] of Object.entries(prop.properties)) {
|
|
636
|
+
nestedProperties[key] = toGeminiSchema(nestedProp);
|
|
637
|
+
}
|
|
638
|
+
schema.properties = nestedProperties;
|
|
639
|
+
if (prop.required !== void 0) {
|
|
640
|
+
schema.required = prop.required;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return schema;
|
|
644
|
+
}
|
|
304
645
|
function createGeminiExtension(config) {
|
|
305
646
|
validateApiKey(config.api_key);
|
|
306
647
|
validateModel(config.model);
|
|
@@ -334,8 +675,8 @@ function createGeminiExtension(config) {
|
|
|
334
675
|
// IR-4: gemini::message
|
|
335
676
|
message: {
|
|
336
677
|
params: [
|
|
337
|
-
|
|
338
|
-
|
|
678
|
+
p.str("text"),
|
|
679
|
+
p.dict("options", void 0, {})
|
|
339
680
|
],
|
|
340
681
|
fn: async (args, ctx) => {
|
|
341
682
|
const startTime = Date.now();
|
|
@@ -343,7 +684,7 @@ function createGeminiExtension(config) {
|
|
|
343
684
|
const text = args[0];
|
|
344
685
|
const options = args[1] ?? {};
|
|
345
686
|
if (text.trim().length === 0) {
|
|
346
|
-
throw new
|
|
687
|
+
throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
|
|
347
688
|
}
|
|
348
689
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
349
690
|
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
@@ -392,12 +733,14 @@ function createGeminiExtension(config) {
|
|
|
392
733
|
subsystem: "extension:gemini",
|
|
393
734
|
duration,
|
|
394
735
|
model: factoryModel,
|
|
395
|
-
usage: result2.usage
|
|
736
|
+
usage: result2.usage,
|
|
737
|
+
request: contents,
|
|
738
|
+
content
|
|
396
739
|
});
|
|
397
740
|
return result2;
|
|
398
741
|
} catch (error) {
|
|
399
742
|
const duration = Date.now() - startTime;
|
|
400
|
-
const rillError = error instanceof
|
|
743
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
401
744
|
emitExtensionEvent(ctx, {
|
|
402
745
|
event: "gemini:error",
|
|
403
746
|
subsystem: "extension:gemini",
|
|
@@ -408,13 +751,13 @@ function createGeminiExtension(config) {
|
|
|
408
751
|
}
|
|
409
752
|
},
|
|
410
753
|
description: "Send single message to Gemini API",
|
|
411
|
-
returnType: "dict"
|
|
754
|
+
returnType: { type: "dict" }
|
|
412
755
|
},
|
|
413
756
|
// IR-5: gemini::messages
|
|
414
757
|
messages: {
|
|
415
758
|
params: [
|
|
416
|
-
|
|
417
|
-
|
|
759
|
+
p.list("messages"),
|
|
760
|
+
p.dict("options", void 0, {})
|
|
418
761
|
],
|
|
419
762
|
fn: async (args, ctx) => {
|
|
420
763
|
const startTime = Date.now();
|
|
@@ -422,7 +765,7 @@ function createGeminiExtension(config) {
|
|
|
422
765
|
const messages = args[0];
|
|
423
766
|
const options = args[1] ?? {};
|
|
424
767
|
if (messages.length === 0) {
|
|
425
|
-
throw new
|
|
768
|
+
throw new RuntimeError6(
|
|
426
769
|
"RILL-R004",
|
|
427
770
|
"messages list cannot be empty"
|
|
428
771
|
);
|
|
@@ -433,18 +776,18 @@ function createGeminiExtension(config) {
|
|
|
433
776
|
for (let i = 0; i < messages.length; i++) {
|
|
434
777
|
const msg = messages[i];
|
|
435
778
|
if (!msg || typeof msg !== "object" || !("role" in msg)) {
|
|
436
|
-
throw new
|
|
779
|
+
throw new RuntimeError6(
|
|
437
780
|
"RILL-R004",
|
|
438
781
|
"message missing required 'role' field"
|
|
439
782
|
);
|
|
440
783
|
}
|
|
441
784
|
const role = msg["role"];
|
|
442
785
|
if (role !== "user" && role !== "assistant" && role !== "tool") {
|
|
443
|
-
throw new
|
|
786
|
+
throw new RuntimeError6("RILL-R004", `invalid role '${role}'`);
|
|
444
787
|
}
|
|
445
788
|
if (role === "user" || role === "tool") {
|
|
446
789
|
if (!("content" in msg) || typeof msg["content"] !== "string") {
|
|
447
|
-
throw new
|
|
790
|
+
throw new RuntimeError6(
|
|
448
791
|
"RILL-R004",
|
|
449
792
|
`${role} message requires 'content'`
|
|
450
793
|
);
|
|
@@ -457,7 +800,7 @@ function createGeminiExtension(config) {
|
|
|
457
800
|
const hasContent = "content" in msg && msg["content"];
|
|
458
801
|
const hasToolCalls = "tool_calls" in msg && msg["tool_calls"];
|
|
459
802
|
if (!hasContent && !hasToolCalls) {
|
|
460
|
-
throw new
|
|
803
|
+
throw new RuntimeError6(
|
|
461
804
|
"RILL-R004",
|
|
462
805
|
"assistant message requires 'content' or 'tool_calls'"
|
|
463
806
|
);
|
|
@@ -514,12 +857,14 @@ function createGeminiExtension(config) {
|
|
|
514
857
|
subsystem: "extension:gemini",
|
|
515
858
|
duration,
|
|
516
859
|
model: factoryModel,
|
|
517
|
-
usage: result2.usage
|
|
860
|
+
usage: result2.usage,
|
|
861
|
+
request: contents,
|
|
862
|
+
content
|
|
518
863
|
});
|
|
519
864
|
return result2;
|
|
520
865
|
} catch (error) {
|
|
521
866
|
const duration = Date.now() - startTime;
|
|
522
|
-
const rillError = error instanceof
|
|
867
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
523
868
|
emitExtensionEvent(ctx, {
|
|
524
869
|
event: "gemini:error",
|
|
525
870
|
subsystem: "extension:gemini",
|
|
@@ -530,11 +875,11 @@ function createGeminiExtension(config) {
|
|
|
530
875
|
}
|
|
531
876
|
},
|
|
532
877
|
description: "Send multi-turn conversation to Gemini API",
|
|
533
|
-
returnType: "dict"
|
|
878
|
+
returnType: { type: "dict" }
|
|
534
879
|
},
|
|
535
880
|
// IR-6: gemini::embed
|
|
536
881
|
embed: {
|
|
537
|
-
params: [
|
|
882
|
+
params: [p.str("text")],
|
|
538
883
|
fn: async (args, ctx) => {
|
|
539
884
|
const startTime = Date.now();
|
|
540
885
|
try {
|
|
@@ -547,7 +892,7 @@ function createGeminiExtension(config) {
|
|
|
547
892
|
});
|
|
548
893
|
const embedding = response.embeddings?.[0];
|
|
549
894
|
if (!embedding || !embedding.values || embedding.values.length === 0) {
|
|
550
|
-
throw new
|
|
895
|
+
throw new RuntimeError6(
|
|
551
896
|
"RILL-R004",
|
|
552
897
|
"Gemini: empty embedding returned"
|
|
553
898
|
);
|
|
@@ -565,7 +910,7 @@ function createGeminiExtension(config) {
|
|
|
565
910
|
return vector;
|
|
566
911
|
} catch (error) {
|
|
567
912
|
const duration = Date.now() - startTime;
|
|
568
|
-
const rillError = error instanceof
|
|
913
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
569
914
|
emitExtensionEvent(ctx, {
|
|
570
915
|
event: "gemini:error",
|
|
571
916
|
subsystem: "extension:gemini",
|
|
@@ -576,11 +921,11 @@ function createGeminiExtension(config) {
|
|
|
576
921
|
}
|
|
577
922
|
},
|
|
578
923
|
description: "Generate embedding vector for text",
|
|
579
|
-
returnType: "vector"
|
|
924
|
+
returnType: { type: "vector" }
|
|
580
925
|
},
|
|
581
926
|
// IR-7: gemini::embed_batch
|
|
582
927
|
embed_batch: {
|
|
583
|
-
params: [
|
|
928
|
+
params: [p.list("texts")],
|
|
584
929
|
fn: async (args, ctx) => {
|
|
585
930
|
const startTime = Date.now();
|
|
586
931
|
try {
|
|
@@ -596,14 +941,14 @@ function createGeminiExtension(config) {
|
|
|
596
941
|
});
|
|
597
942
|
const vectors = [];
|
|
598
943
|
if (!response.embeddings || response.embeddings.length === 0) {
|
|
599
|
-
throw new
|
|
944
|
+
throw new RuntimeError6(
|
|
600
945
|
"RILL-R004",
|
|
601
946
|
"Gemini: empty embeddings returned"
|
|
602
947
|
);
|
|
603
948
|
}
|
|
604
949
|
for (const embedding of response.embeddings) {
|
|
605
950
|
if (!embedding || !embedding.values || embedding.values.length === 0) {
|
|
606
|
-
throw new
|
|
951
|
+
throw new RuntimeError6(
|
|
607
952
|
"RILL-R004",
|
|
608
953
|
"Gemini: empty embedding returned"
|
|
609
954
|
);
|
|
@@ -626,7 +971,7 @@ function createGeminiExtension(config) {
|
|
|
626
971
|
return vectors;
|
|
627
972
|
} catch (error) {
|
|
628
973
|
const duration = Date.now() - startTime;
|
|
629
|
-
const rillError = error instanceof
|
|
974
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
630
975
|
emitExtensionEvent(ctx, {
|
|
631
976
|
event: "gemini:error",
|
|
632
977
|
subsystem: "extension:gemini",
|
|
@@ -637,13 +982,13 @@ function createGeminiExtension(config) {
|
|
|
637
982
|
}
|
|
638
983
|
},
|
|
639
984
|
description: "Generate embedding vectors for multiple texts",
|
|
640
|
-
returnType: "list"
|
|
985
|
+
returnType: { type: "list" }
|
|
641
986
|
},
|
|
642
987
|
// IR-8: gemini::tool_loop
|
|
643
988
|
tool_loop: {
|
|
644
989
|
params: [
|
|
645
|
-
|
|
646
|
-
|
|
990
|
+
p.str("prompt"),
|
|
991
|
+
p.dict("options", void 0, {})
|
|
647
992
|
],
|
|
648
993
|
fn: async (args, ctx) => {
|
|
649
994
|
const startTime = Date.now();
|
|
@@ -651,39 +996,15 @@ function createGeminiExtension(config) {
|
|
|
651
996
|
const prompt = args[0];
|
|
652
997
|
const options = args[1] ?? {};
|
|
653
998
|
if (prompt.trim().length === 0) {
|
|
654
|
-
throw new
|
|
999
|
+
throw new RuntimeError6("RILL-R004", "prompt text cannot be empty");
|
|
655
1000
|
}
|
|
656
|
-
if (!("tools" in options) || !
|
|
657
|
-
throw new
|
|
1001
|
+
if (!("tools" in options) || !isDict2(options["tools"])) {
|
|
1002
|
+
throw new RuntimeError6(
|
|
658
1003
|
"RILL-R004",
|
|
659
1004
|
"tool_loop requires 'tools' option"
|
|
660
1005
|
);
|
|
661
1006
|
}
|
|
662
|
-
const
|
|
663
|
-
const toolsDict = {};
|
|
664
|
-
for (const descriptor of toolDescriptors) {
|
|
665
|
-
const name = typeof descriptor["name"] === "string" ? descriptor["name"] : null;
|
|
666
|
-
if (!name) {
|
|
667
|
-
throw new RuntimeError4(
|
|
668
|
-
"RILL-R004",
|
|
669
|
-
"tool descriptor missing name"
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
const toolFnValue = descriptor["fn"];
|
|
673
|
-
if (!toolFnValue) {
|
|
674
|
-
throw new RuntimeError4(
|
|
675
|
-
"RILL-R004",
|
|
676
|
-
`tool '${name}' missing fn property`
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
if (!isCallable2(toolFnValue)) {
|
|
680
|
-
throw new RuntimeError4(
|
|
681
|
-
"RILL-R004",
|
|
682
|
-
`tool '${name}' fn must be callable`
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
toolsDict[name] = toolFnValue;
|
|
686
|
-
}
|
|
1007
|
+
const toolsDict = options["tools"];
|
|
687
1008
|
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
688
1009
|
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
689
1010
|
const maxTurns = typeof options["max_turns"] === "number" ? options["max_turns"] : 10;
|
|
@@ -778,6 +1099,21 @@ function createGeminiExtension(config) {
|
|
|
778
1099
|
};
|
|
779
1100
|
});
|
|
780
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
|
+
}
|
|
1107
|
+
const candidates = response2.candidates;
|
|
1108
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
const candidate = candidates[0];
|
|
1112
|
+
if (!candidate || typeof candidate !== "object" || !("content" in candidate)) {
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
return candidate.content;
|
|
1116
|
+
},
|
|
781
1117
|
// Format tool results into Gemini message format
|
|
782
1118
|
formatToolResult: (toolResults) => {
|
|
783
1119
|
const functionResponseParts = toolResults.map((tr) => ({
|
|
@@ -810,7 +1146,8 @@ function createGeminiExtension(config) {
|
|
|
810
1146
|
...data
|
|
811
1147
|
});
|
|
812
1148
|
},
|
|
813
|
-
maxTurns
|
|
1149
|
+
maxTurns,
|
|
1150
|
+
ctx
|
|
814
1151
|
);
|
|
815
1152
|
const response = loopResult.response;
|
|
816
1153
|
const content = response && typeof response === "object" && "text" in response ? response.text ?? "" : "";
|
|
@@ -832,12 +1169,14 @@ function createGeminiExtension(config) {
|
|
|
832
1169
|
subsystem: "extension:gemini",
|
|
833
1170
|
turns: result2.turns,
|
|
834
1171
|
total_duration: duration,
|
|
835
|
-
usage: result2.usage
|
|
1172
|
+
usage: result2.usage,
|
|
1173
|
+
request: contents,
|
|
1174
|
+
content
|
|
836
1175
|
});
|
|
837
1176
|
return result2;
|
|
838
1177
|
} catch (error) {
|
|
839
1178
|
const duration = Date.now() - startTime;
|
|
840
|
-
const rillError = error instanceof
|
|
1179
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
841
1180
|
emitExtensionEvent(ctx, {
|
|
842
1181
|
event: "gemini:error",
|
|
843
1182
|
subsystem: "extension:gemini",
|
|
@@ -848,7 +1187,131 @@ function createGeminiExtension(config) {
|
|
|
848
1187
|
}
|
|
849
1188
|
},
|
|
850
1189
|
description: "Execute tool-use loop with Gemini API",
|
|
851
|
-
returnType: "dict"
|
|
1190
|
+
returnType: { type: "dict" }
|
|
1191
|
+
},
|
|
1192
|
+
// IR-3: gemini::generate
|
|
1193
|
+
generate: {
|
|
1194
|
+
params: [
|
|
1195
|
+
p.str("prompt"),
|
|
1196
|
+
p.dict("options")
|
|
1197
|
+
],
|
|
1198
|
+
fn: async (args, ctx) => {
|
|
1199
|
+
const startTime = Date.now();
|
|
1200
|
+
try {
|
|
1201
|
+
const prompt = args[0];
|
|
1202
|
+
const options = args[1] ?? {};
|
|
1203
|
+
if (!("schema" in options) || options["schema"] === null || options["schema"] === void 0) {
|
|
1204
|
+
throw new RuntimeError6(
|
|
1205
|
+
"RILL-R004",
|
|
1206
|
+
"generate requires 'schema' option"
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
const rillSchema = options["schema"];
|
|
1210
|
+
const jsonSchema = buildJsonSchema(rillSchema);
|
|
1211
|
+
const geminiProperties = {};
|
|
1212
|
+
for (const [key, prop] of Object.entries(jsonSchema.properties)) {
|
|
1213
|
+
geminiProperties[key] = toGeminiSchema(prop);
|
|
1214
|
+
}
|
|
1215
|
+
const responseSchema = {
|
|
1216
|
+
type: Type.OBJECT,
|
|
1217
|
+
properties: geminiProperties,
|
|
1218
|
+
required: jsonSchema.required
|
|
1219
|
+
};
|
|
1220
|
+
const system = typeof options["system"] === "string" ? options["system"] : factorySystem;
|
|
1221
|
+
const maxTokens = typeof options["max_tokens"] === "number" ? options["max_tokens"] : factoryMaxTokens;
|
|
1222
|
+
const contents = [];
|
|
1223
|
+
if ("messages" in options && Array.isArray(options["messages"])) {
|
|
1224
|
+
const prependedMessages = options["messages"];
|
|
1225
|
+
for (const msg of prependedMessages) {
|
|
1226
|
+
if (typeof msg === "object" && msg !== null && "role" in msg && "content" in msg) {
|
|
1227
|
+
const role = msg["role"];
|
|
1228
|
+
if (role === "user") {
|
|
1229
|
+
contents.push({
|
|
1230
|
+
role: "user",
|
|
1231
|
+
parts: [{ text: msg["content"] }]
|
|
1232
|
+
});
|
|
1233
|
+
} else if (role === "assistant") {
|
|
1234
|
+
contents.push({
|
|
1235
|
+
role: "model",
|
|
1236
|
+
parts: [{ text: msg["content"] }]
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
contents.push({
|
|
1243
|
+
role: "user",
|
|
1244
|
+
parts: [{ text: prompt }]
|
|
1245
|
+
});
|
|
1246
|
+
const apiConfig = {
|
|
1247
|
+
responseSchema,
|
|
1248
|
+
responseMimeType: "application/json"
|
|
1249
|
+
};
|
|
1250
|
+
if (system !== void 0) {
|
|
1251
|
+
apiConfig.systemInstruction = system;
|
|
1252
|
+
}
|
|
1253
|
+
if (maxTokens !== void 0) {
|
|
1254
|
+
apiConfig.maxOutputTokens = maxTokens;
|
|
1255
|
+
}
|
|
1256
|
+
if (factoryTemperature !== void 0) {
|
|
1257
|
+
apiConfig.temperature = factoryTemperature;
|
|
1258
|
+
}
|
|
1259
|
+
const response = await client.models.generateContent({
|
|
1260
|
+
model: factoryModel,
|
|
1261
|
+
contents,
|
|
1262
|
+
config: apiConfig
|
|
1263
|
+
});
|
|
1264
|
+
const raw = response.text ?? "";
|
|
1265
|
+
let data;
|
|
1266
|
+
try {
|
|
1267
|
+
data = JSON.parse(raw);
|
|
1268
|
+
} catch (parseError) {
|
|
1269
|
+
const detail = parseError instanceof Error ? parseError.message : String(parseError);
|
|
1270
|
+
throw new RuntimeError6(
|
|
1271
|
+
"RILL-R004",
|
|
1272
|
+
`generate: failed to parse response JSON: ${detail}`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
const inputTokens = response.usageMetadata?.promptTokenCount ?? 0;
|
|
1276
|
+
const outputTokens = response.usageMetadata?.candidatesTokenCount ?? 0;
|
|
1277
|
+
const stopReason = response.candidates?.[0]?.finishReason ?? "stop";
|
|
1278
|
+
const id = response.responseId ?? "";
|
|
1279
|
+
const generateResult = {
|
|
1280
|
+
data,
|
|
1281
|
+
raw,
|
|
1282
|
+
model: factoryModel,
|
|
1283
|
+
usage: {
|
|
1284
|
+
input: inputTokens,
|
|
1285
|
+
output: outputTokens
|
|
1286
|
+
},
|
|
1287
|
+
stop_reason: stopReason,
|
|
1288
|
+
id
|
|
1289
|
+
};
|
|
1290
|
+
const duration = Date.now() - startTime;
|
|
1291
|
+
emitExtensionEvent(ctx, {
|
|
1292
|
+
event: "gemini:generate",
|
|
1293
|
+
subsystem: "extension:gemini",
|
|
1294
|
+
duration,
|
|
1295
|
+
model: factoryModel,
|
|
1296
|
+
usage: generateResult.usage,
|
|
1297
|
+
request: contents,
|
|
1298
|
+
content: raw
|
|
1299
|
+
});
|
|
1300
|
+
return generateResult;
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
const duration = Date.now() - startTime;
|
|
1303
|
+
const rillError = error instanceof RuntimeError6 ? error : mapProviderError("Gemini", error, detectGeminiError);
|
|
1304
|
+
emitExtensionEvent(ctx, {
|
|
1305
|
+
event: "gemini:error",
|
|
1306
|
+
subsystem: "extension:gemini",
|
|
1307
|
+
error: rillError.message,
|
|
1308
|
+
duration
|
|
1309
|
+
});
|
|
1310
|
+
throw rillError;
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
description: "Generate structured output from Gemini API",
|
|
1314
|
+
returnType: { type: "dict" }
|
|
852
1315
|
}
|
|
853
1316
|
};
|
|
854
1317
|
result.dispose = dispose;
|
|
@@ -857,7 +1320,19 @@ function createGeminiExtension(config) {
|
|
|
857
1320
|
|
|
858
1321
|
// src/index.ts
|
|
859
1322
|
var VERSION = "0.0.1";
|
|
1323
|
+
var configSchema = {
|
|
1324
|
+
api_key: { type: "string", required: true, secret: true },
|
|
1325
|
+
model: { type: "string", required: true },
|
|
1326
|
+
base_url: { type: "string" },
|
|
1327
|
+
temperature: { type: "number" },
|
|
1328
|
+
max_tokens: { type: "number" },
|
|
1329
|
+
timeout: { type: "number" },
|
|
1330
|
+
max_retries: { type: "number" },
|
|
1331
|
+
system: { type: "string" },
|
|
1332
|
+
embed_model: { type: "string" }
|
|
1333
|
+
};
|
|
860
1334
|
export {
|
|
861
1335
|
VERSION,
|
|
1336
|
+
configSchema,
|
|
862
1337
|
createGeminiExtension
|
|
863
1338
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rcrsr/rill-ext-gemini",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "rill extension for Google Gemini API integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Andre Bremer",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"scripting"
|
|
18
18
|
],
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@rcrsr/rill": "^0.
|
|
20
|
+
"@rcrsr/rill": "^0.11.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@
|
|
23
|
+
"@rcrsr/rill": "^0.11.0",
|
|
24
|
+
"@types/node": "^25.3.0",
|
|
24
25
|
"dts-bundle-generator": "^9.5.1",
|
|
25
|
-
"tsup": "^8.5.
|
|
26
|
-
"undici-types": "^7.
|
|
27
|
-
"@rcrsr/rill": "^0.8.6",
|
|
26
|
+
"tsup": "^8.5.1",
|
|
27
|
+
"undici-types": "^7.22.0",
|
|
28
28
|
"@rcrsr/rill-ext-llm-shared": "^0.0.1"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
@@ -32,18 +32,19 @@
|
|
|
32
32
|
],
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/rcrsr/rill.git",
|
|
35
|
+
"url": "git+https://github.com/rcrsr/rill-ext.git",
|
|
36
36
|
"directory": "packages/ext/llm-gemini"
|
|
37
37
|
},
|
|
38
|
-
"homepage": "https://
|
|
38
|
+
"homepage": "https://github.com/rcrsr/rill-ext/tree/main/packages/ext/llm-gemini#readme",
|
|
39
39
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/rcrsr/rill/issues"
|
|
40
|
+
"url": "https://github.com/rcrsr/rill-ext/issues"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@google/genai": "^1.
|
|
46
|
+
"@google/genai": "^1.42.0",
|
|
47
|
+
"@rcrsr/rill-ext-param-shared": "0.0.1"
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"build": "tsup && dts-bundle-generator --config dts-bundle-generator.config.cjs",
|