@brianli/kimaki 0.4.72-brianli.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin.js +2 -0
- package/dist/ai-tool-to-genai.js +233 -0
- package/dist/ai-tool-to-genai.test.js +267 -0
- package/dist/ai-tool.js +6 -0
- package/dist/bin.js +87 -0
- package/dist/bot-token.js +121 -0
- package/dist/bot-token.test.js +134 -0
- package/dist/channel-management.js +101 -0
- package/dist/cli-parsing.test.js +89 -0
- package/dist/cli.js +2529 -0
- package/dist/commands/abort.js +82 -0
- package/dist/commands/action-buttons.js +257 -0
- package/dist/commands/add-project.js +114 -0
- package/dist/commands/agent.js +291 -0
- package/dist/commands/ask-question.js +223 -0
- package/dist/commands/compact.js +120 -0
- package/dist/commands/context-usage.js +140 -0
- package/dist/commands/create-new-project.js +118 -0
- package/dist/commands/diff.js +128 -0
- package/dist/commands/file-upload.js +275 -0
- package/dist/commands/fork.js +217 -0
- package/dist/commands/gemini-apikey.js +70 -0
- package/dist/commands/login.js +490 -0
- package/dist/commands/mention-mode.js +51 -0
- package/dist/commands/merge-worktree.js +124 -0
- package/dist/commands/model.js +694 -0
- package/dist/commands/permissions.js +163 -0
- package/dist/commands/queue.js +217 -0
- package/dist/commands/remove-project.js +115 -0
- package/dist/commands/restart-opencode-server.js +116 -0
- package/dist/commands/resume.js +159 -0
- package/dist/commands/run-command.js +79 -0
- package/dist/commands/session-id.js +78 -0
- package/dist/commands/session.js +192 -0
- package/dist/commands/share.js +80 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/undo-redo.js +159 -0
- package/dist/commands/unset-model.js +152 -0
- package/dist/commands/upgrade.js +42 -0
- package/dist/commands/user-command.js +148 -0
- package/dist/commands/verbosity.js +60 -0
- package/dist/commands/worktree-settings.js +50 -0
- package/dist/commands/worktree.js +299 -0
- package/dist/condense-memory.js +33 -0
- package/dist/config.js +110 -0
- package/dist/database.js +1050 -0
- package/dist/db.js +159 -0
- package/dist/db.test.js +49 -0
- package/dist/discord-api.js +28 -0
- package/dist/discord-auth.js +231 -0
- package/dist/discord-auth.test.js +80 -0
- package/dist/discord-bot.js +997 -0
- package/dist/discord-utils.js +560 -0
- package/dist/discord-utils.test.js +115 -0
- package/dist/errors.js +167 -0
- package/dist/escape-backticks.test.js +429 -0
- package/dist/format-tables.js +122 -0
- package/dist/format-tables.test.js +199 -0
- package/dist/forum-sync/config.js +79 -0
- package/dist/forum-sync/discord-operations.js +154 -0
- package/dist/forum-sync/index.js +5 -0
- package/dist/forum-sync/markdown.js +117 -0
- package/dist/forum-sync/sync-to-discord.js +417 -0
- package/dist/forum-sync/sync-to-files.js +190 -0
- package/dist/forum-sync/types.js +53 -0
- package/dist/forum-sync/watchers.js +307 -0
- package/dist/gateway-consumer.js +232 -0
- package/dist/gateway-consumer.test.js +18 -0
- package/dist/genai-worker-wrapper.js +111 -0
- package/dist/genai-worker.js +311 -0
- package/dist/genai.js +232 -0
- package/dist/generated/browser.js +17 -0
- package/dist/generated/client.js +35 -0
- package/dist/generated/commonInputTypes.js +10 -0
- package/dist/generated/enums.js +30 -0
- package/dist/generated/internal/class.js +41 -0
- package/dist/generated/internal/prismaNamespace.js +239 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
- package/dist/generated/models/bot_api_keys.js +1 -0
- package/dist/generated/models/bot_tokens.js +1 -0
- package/dist/generated/models/channel_agents.js +1 -0
- package/dist/generated/models/channel_directories.js +1 -0
- package/dist/generated/models/channel_mention_mode.js +1 -0
- package/dist/generated/models/channel_models.js +1 -0
- package/dist/generated/models/channel_verbosity.js +1 -0
- package/dist/generated/models/channel_worktrees.js +1 -0
- package/dist/generated/models/forum_sync_configs.js +1 -0
- package/dist/generated/models/global_models.js +1 -0
- package/dist/generated/models/ipc_requests.js +1 -0
- package/dist/generated/models/part_messages.js +1 -0
- package/dist/generated/models/scheduled_tasks.js +1 -0
- package/dist/generated/models/session_agents.js +1 -0
- package/dist/generated/models/session_models.js +1 -0
- package/dist/generated/models/session_start_sources.js +1 -0
- package/dist/generated/models/thread_sessions.js +1 -0
- package/dist/generated/models/thread_worktrees.js +1 -0
- package/dist/generated/models.js +1 -0
- package/dist/heap-monitor.js +95 -0
- package/dist/hrana-server.js +416 -0
- package/dist/hrana-server.test.js +368 -0
- package/dist/image-utils.js +112 -0
- package/dist/interaction-handler.js +327 -0
- package/dist/ipc-polling.js +251 -0
- package/dist/kimaki-digital-twin.e2e.test.js +165 -0
- package/dist/limit-heading-depth.js +25 -0
- package/dist/limit-heading-depth.test.js +105 -0
- package/dist/logger.js +160 -0
- package/dist/markdown.js +342 -0
- package/dist/markdown.test.js +253 -0
- package/dist/message-formatting.js +433 -0
- package/dist/message-formatting.test.js +73 -0
- package/dist/openai-realtime.js +228 -0
- package/dist/opencode-plugin-loading.e2e.test.js +91 -0
- package/dist/opencode-plugin.js +536 -0
- package/dist/opencode-plugin.test.js +98 -0
- package/dist/opencode.js +409 -0
- package/dist/privacy-sanitizer.js +105 -0
- package/dist/runtime-mode.js +51 -0
- package/dist/runtime-mode.test.js +115 -0
- package/dist/sentry.js +127 -0
- package/dist/session-handler/state.js +151 -0
- package/dist/session-handler.js +1874 -0
- package/dist/session-search.js +100 -0
- package/dist/session-search.test.js +40 -0
- package/dist/startup-service.js +153 -0
- package/dist/system-message.js +499 -0
- package/dist/task-runner.js +282 -0
- package/dist/task-schedule.js +191 -0
- package/dist/task-schedule.test.js +71 -0
- package/dist/thinking-utils.js +35 -0
- package/dist/thread-message-queue.e2e.test.js +781 -0
- package/dist/tools.js +359 -0
- package/dist/unnest-code-blocks.js +136 -0
- package/dist/unnest-code-blocks.test.js +641 -0
- package/dist/upgrade.js +114 -0
- package/dist/utils.js +109 -0
- package/dist/voice-handler.js +606 -0
- package/dist/voice.js +304 -0
- package/dist/voice.test.js +187 -0
- package/dist/wait-session.js +94 -0
- package/dist/worker-types.js +4 -0
- package/dist/worktree-utils.js +727 -0
- package/dist/xml.js +92 -0
- package/dist/xml.test.js +32 -0
- package/package.json +82 -0
- package/schema.prisma +246 -0
- package/skills/batch/SKILL.md +87 -0
- package/skills/critique/SKILL.md +129 -0
- package/skills/errore/SKILL.md +589 -0
- package/skills/goke/.prettierrc +5 -0
- package/skills/goke/CHANGELOG.md +40 -0
- package/skills/goke/LICENSE +21 -0
- package/skills/goke/README.md +666 -0
- package/skills/goke/SKILL.md +458 -0
- package/skills/goke/package.json +43 -0
- package/skills/goke/src/__test__/coerce.test.ts +411 -0
- package/skills/goke/src/__test__/index.test.ts +1798 -0
- package/skills/goke/src/__test__/types.test-d.ts +111 -0
- package/skills/goke/src/coerce.ts +547 -0
- package/skills/goke/src/goke.ts +1362 -0
- package/skills/goke/src/index.ts +16 -0
- package/skills/goke/src/mri.ts +164 -0
- package/skills/goke/tsconfig.json +15 -0
- package/skills/jitter/EDITOR.md +219 -0
- package/skills/jitter/EXPORT-INTERNALS.md +309 -0
- package/skills/jitter/SKILL.md +158 -0
- package/skills/jitter/jitter-clipboard.json +1042 -0
- package/skills/jitter/package.json +14 -0
- package/skills/jitter/tsconfig.json +15 -0
- package/skills/jitter/utils/actions.ts +212 -0
- package/skills/jitter/utils/export.ts +114 -0
- package/skills/jitter/utils/index.ts +141 -0
- package/skills/jitter/utils/snapshot.ts +154 -0
- package/skills/jitter/utils/traverse.ts +246 -0
- package/skills/jitter/utils/types.ts +279 -0
- package/skills/jitter/utils/wait.ts +133 -0
- package/skills/playwriter/SKILL.md +31 -0
- package/skills/security-review/SKILL.md +208 -0
- package/skills/simplify/SKILL.md +58 -0
- package/skills/termcast/SKILL.md +945 -0
- package/skills/tuistory/SKILL.md +250 -0
- package/skills/zustand-centralized-state/SKILL.md +582 -0
- package/src/__snapshots__/compact-session-context-no-system.md +35 -0
- package/src/__snapshots__/compact-session-context.md +41 -0
- package/src/__snapshots__/first-session-no-info.md +17 -0
- package/src/__snapshots__/first-session-with-info.md +23 -0
- package/src/__snapshots__/session-1.md +17 -0
- package/src/__snapshots__/session-2.md +5871 -0
- package/src/__snapshots__/session-3.md +17 -0
- package/src/__snapshots__/session-with-tools.md +5871 -0
- package/src/ai-tool-to-genai.test.ts +296 -0
- package/src/ai-tool-to-genai.ts +282 -0
- package/src/ai-tool.ts +39 -0
- package/src/bin.ts +108 -0
- package/src/bot-token.test.ts +171 -0
- package/src/bot-token.ts +159 -0
- package/src/channel-management.ts +172 -0
- package/src/cli-parsing.test.ts +132 -0
- package/src/cli.ts +3605 -0
- package/src/commands/abort.ts +112 -0
- package/src/commands/action-buttons.ts +376 -0
- package/src/commands/add-project.ts +152 -0
- package/src/commands/agent.ts +404 -0
- package/src/commands/ask-question.ts +330 -0
- package/src/commands/compact.ts +157 -0
- package/src/commands/context-usage.ts +199 -0
- package/src/commands/create-new-project.ts +179 -0
- package/src/commands/diff.ts +165 -0
- package/src/commands/file-upload.ts +389 -0
- package/src/commands/fork.ts +320 -0
- package/src/commands/gemini-apikey.ts +104 -0
- package/src/commands/login.ts +634 -0
- package/src/commands/mention-mode.ts +77 -0
- package/src/commands/merge-worktree.ts +177 -0
- package/src/commands/model.ts +961 -0
- package/src/commands/permissions.ts +261 -0
- package/src/commands/queue.ts +296 -0
- package/src/commands/remove-project.ts +155 -0
- package/src/commands/restart-opencode-server.ts +162 -0
- package/src/commands/resume.ts +242 -0
- package/src/commands/run-command.ts +123 -0
- package/src/commands/session-id.ts +109 -0
- package/src/commands/session.ts +250 -0
- package/src/commands/share.ts +106 -0
- package/src/commands/types.ts +25 -0
- package/src/commands/undo-redo.ts +221 -0
- package/src/commands/unset-model.ts +189 -0
- package/src/commands/upgrade.ts +52 -0
- package/src/commands/user-command.ts +193 -0
- package/src/commands/verbosity.ts +88 -0
- package/src/commands/worktree-settings.ts +79 -0
- package/src/commands/worktree.ts +431 -0
- package/src/condense-memory.ts +36 -0
- package/src/config.ts +148 -0
- package/src/database.ts +1530 -0
- package/src/db.test.ts +60 -0
- package/src/db.ts +190 -0
- package/src/discord-api.ts +35 -0
- package/src/discord-bot.ts +1316 -0
- package/src/discord-utils.test.ts +132 -0
- package/src/discord-utils.ts +767 -0
- package/src/errors.ts +213 -0
- package/src/escape-backticks.test.ts +469 -0
- package/src/format-tables.test.ts +223 -0
- package/src/format-tables.ts +145 -0
- package/src/forum-sync/config.ts +92 -0
- package/src/forum-sync/discord-operations.ts +241 -0
- package/src/forum-sync/index.ts +9 -0
- package/src/forum-sync/markdown.ts +176 -0
- package/src/forum-sync/sync-to-discord.ts +595 -0
- package/src/forum-sync/sync-to-files.ts +294 -0
- package/src/forum-sync/types.ts +175 -0
- package/src/forum-sync/watchers.ts +454 -0
- package/src/genai-worker-wrapper.ts +164 -0
- package/src/genai-worker.ts +386 -0
- package/src/genai.ts +321 -0
- package/src/generated/browser.ts +109 -0
- package/src/generated/client.ts +131 -0
- package/src/generated/commonInputTypes.ts +512 -0
- package/src/generated/enums.ts +46 -0
- package/src/generated/internal/class.ts +362 -0
- package/src/generated/internal/prismaNamespace.ts +2251 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
- package/src/generated/models/bot_api_keys.ts +1288 -0
- package/src/generated/models/bot_tokens.ts +1577 -0
- package/src/generated/models/channel_agents.ts +1256 -0
- package/src/generated/models/channel_directories.ts +2104 -0
- package/src/generated/models/channel_mention_mode.ts +1300 -0
- package/src/generated/models/channel_models.ts +1288 -0
- package/src/generated/models/channel_verbosity.ts +1224 -0
- package/src/generated/models/channel_worktrees.ts +1308 -0
- package/src/generated/models/forum_sync_configs.ts +1452 -0
- package/src/generated/models/global_models.ts +1288 -0
- package/src/generated/models/ipc_requests.ts +1485 -0
- package/src/generated/models/part_messages.ts +1302 -0
- package/src/generated/models/scheduled_tasks.ts +2320 -0
- package/src/generated/models/session_agents.ts +1086 -0
- package/src/generated/models/session_models.ts +1114 -0
- package/src/generated/models/session_start_sources.ts +1408 -0
- package/src/generated/models/thread_sessions.ts +1599 -0
- package/src/generated/models/thread_worktrees.ts +1352 -0
- package/src/generated/models.ts +29 -0
- package/src/heap-monitor.ts +121 -0
- package/src/hrana-server.test.ts +428 -0
- package/src/hrana-server.ts +547 -0
- package/src/image-utils.ts +149 -0
- package/src/interaction-handler.ts +461 -0
- package/src/ipc-polling.ts +325 -0
- package/src/kimaki-digital-twin.e2e.test.ts +201 -0
- package/src/limit-heading-depth.test.ts +116 -0
- package/src/limit-heading-depth.ts +26 -0
- package/src/logger.ts +203 -0
- package/src/markdown.test.ts +360 -0
- package/src/markdown.ts +410 -0
- package/src/message-formatting.test.ts +81 -0
- package/src/message-formatting.ts +549 -0
- package/src/openai-realtime.ts +362 -0
- package/src/opencode-plugin-loading.e2e.test.ts +112 -0
- package/src/opencode-plugin.test.ts +108 -0
- package/src/opencode-plugin.ts +652 -0
- package/src/opencode.ts +554 -0
- package/src/privacy-sanitizer.ts +142 -0
- package/src/schema.sql +158 -0
- package/src/sentry.ts +137 -0
- package/src/session-handler/state.ts +232 -0
- package/src/session-handler.ts +2668 -0
- package/src/session-search.test.ts +50 -0
- package/src/session-search.ts +148 -0
- package/src/startup-service.ts +200 -0
- package/src/system-message.ts +568 -0
- package/src/task-runner.ts +425 -0
- package/src/task-schedule.test.ts +84 -0
- package/src/task-schedule.ts +287 -0
- package/src/thinking-utils.ts +61 -0
- package/src/thread-message-queue.e2e.test.ts +997 -0
- package/src/tools.ts +432 -0
- package/src/unnest-code-blocks.test.ts +679 -0
- package/src/unnest-code-blocks.ts +168 -0
- package/src/upgrade.ts +127 -0
- package/src/utils.ts +145 -0
- package/src/voice-handler.ts +852 -0
- package/src/voice.test.ts +219 -0
- package/src/voice.ts +444 -0
- package/src/wait-session.ts +147 -0
- package/src/worker-types.ts +64 -0
- package/src/worktree-utils.ts +988 -0
- package/src/xml.test.ts +38 -0
- package/src/xml.ts +121 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { tool } from './ai-tool.js'
|
|
3
|
+
import { z } from 'zod'
|
|
4
|
+
import { Type } from '@google/genai'
|
|
5
|
+
import type { FunctionDeclaration, FunctionCall } from '@google/genai'
|
|
6
|
+
import {
|
|
7
|
+
aiToolToGenAIFunction,
|
|
8
|
+
aiToolToCallableTool,
|
|
9
|
+
extractSchemaFromTool,
|
|
10
|
+
} from './ai-tool-to-genai.js'
|
|
11
|
+
|
|
12
|
+
describe('AI Tool to GenAI Conversion', () => {
|
|
13
|
+
it('should convert a simple Zod-based tool', () => {
|
|
14
|
+
const weatherTool = tool({
|
|
15
|
+
description: 'Get the current weather for a location',
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
location: z.string().describe('The city name'),
|
|
18
|
+
unit: z.enum(['celsius', 'fahrenheit']).optional(),
|
|
19
|
+
}),
|
|
20
|
+
execute: async ({ location, unit }) => {
|
|
21
|
+
return {
|
|
22
|
+
temperature: 72,
|
|
23
|
+
unit: unit || 'fahrenheit',
|
|
24
|
+
condition: 'sunny',
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const genAIFunction = aiToolToGenAIFunction(weatherTool)
|
|
30
|
+
|
|
31
|
+
expect(genAIFunction).toMatchInlineSnapshot(`
|
|
32
|
+
{
|
|
33
|
+
"description": "Get the current weather for a location",
|
|
34
|
+
"name": "tool",
|
|
35
|
+
"parameters": {
|
|
36
|
+
"properties": {
|
|
37
|
+
"location": {
|
|
38
|
+
"description": "The city name",
|
|
39
|
+
"type": "STRING",
|
|
40
|
+
},
|
|
41
|
+
"unit": {
|
|
42
|
+
"enum": [
|
|
43
|
+
"celsius",
|
|
44
|
+
"fahrenheit",
|
|
45
|
+
],
|
|
46
|
+
"type": "STRING",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"required": [
|
|
50
|
+
"location",
|
|
51
|
+
],
|
|
52
|
+
"type": "OBJECT",
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
`)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should handle complex nested schemas', () => {
|
|
59
|
+
const complexTool = tool({
|
|
60
|
+
description: 'Process complex data',
|
|
61
|
+
inputSchema: z.object({
|
|
62
|
+
user: z.object({
|
|
63
|
+
name: z.string(),
|
|
64
|
+
age: z.number().int().min(0).max(150),
|
|
65
|
+
email: z.string().email(),
|
|
66
|
+
}),
|
|
67
|
+
preferences: z.array(z.string()),
|
|
68
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
69
|
+
}),
|
|
70
|
+
execute: async (input) => input,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const genAIFunction = aiToolToGenAIFunction(complexTool)
|
|
74
|
+
|
|
75
|
+
expect(genAIFunction.parameters).toMatchInlineSnapshot(`
|
|
76
|
+
{
|
|
77
|
+
"properties": {
|
|
78
|
+
"metadata": {
|
|
79
|
+
"type": "OBJECT",
|
|
80
|
+
},
|
|
81
|
+
"preferences": {
|
|
82
|
+
"items": {
|
|
83
|
+
"type": "STRING",
|
|
84
|
+
},
|
|
85
|
+
"type": "ARRAY",
|
|
86
|
+
},
|
|
87
|
+
"user": {
|
|
88
|
+
"properties": {
|
|
89
|
+
"age": {
|
|
90
|
+
"format": "int32",
|
|
91
|
+
"maximum": 150,
|
|
92
|
+
"minimum": 0,
|
|
93
|
+
"type": "INTEGER",
|
|
94
|
+
},
|
|
95
|
+
"email": {
|
|
96
|
+
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
|
|
97
|
+
"type": "STRING",
|
|
98
|
+
},
|
|
99
|
+
"name": {
|
|
100
|
+
"type": "STRING",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
"required": [
|
|
104
|
+
"name",
|
|
105
|
+
"age",
|
|
106
|
+
"email",
|
|
107
|
+
],
|
|
108
|
+
"type": "OBJECT",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
"required": [
|
|
112
|
+
"user",
|
|
113
|
+
"preferences",
|
|
114
|
+
],
|
|
115
|
+
"type": "OBJECT",
|
|
116
|
+
}
|
|
117
|
+
`)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should extract schema from tool', () => {
|
|
121
|
+
const testTool = tool({
|
|
122
|
+
inputSchema: z.object({
|
|
123
|
+
test: z.string(),
|
|
124
|
+
}),
|
|
125
|
+
execute: async () => {},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const schema = extractSchemaFromTool(testTool)
|
|
129
|
+
|
|
130
|
+
expect(schema).toMatchInlineSnapshot(`
|
|
131
|
+
{
|
|
132
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
133
|
+
"additionalProperties": false,
|
|
134
|
+
"properties": {
|
|
135
|
+
"test": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
"required": [
|
|
140
|
+
"test",
|
|
141
|
+
],
|
|
142
|
+
"type": "object",
|
|
143
|
+
}
|
|
144
|
+
`)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should handle tools with no input schema', () => {
|
|
148
|
+
const simpleTool = tool({
|
|
149
|
+
description: 'Simple tool with no inputs',
|
|
150
|
+
inputSchema: z.object({}),
|
|
151
|
+
execute: async () => ({ result: 'done' }),
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const genAIFunction = aiToolToGenAIFunction(simpleTool)
|
|
155
|
+
|
|
156
|
+
expect(genAIFunction).toMatchInlineSnapshot(`
|
|
157
|
+
{
|
|
158
|
+
"description": "Simple tool with no inputs",
|
|
159
|
+
"name": "tool",
|
|
160
|
+
"parameters": {
|
|
161
|
+
"properties": {},
|
|
162
|
+
"type": "OBJECT",
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
`)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should handle union types', () => {
|
|
169
|
+
const unionTool = tool({
|
|
170
|
+
description: 'Tool with union types',
|
|
171
|
+
inputSchema: z.object({
|
|
172
|
+
value: z.union([z.string(), z.number(), z.boolean()]),
|
|
173
|
+
}),
|
|
174
|
+
execute: async ({ value }) => ({ received: value }),
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const genAIFunction = aiToolToGenAIFunction(unionTool)
|
|
178
|
+
|
|
179
|
+
expect(genAIFunction.parameters?.properties?.value).toMatchInlineSnapshot(`
|
|
180
|
+
{
|
|
181
|
+
"anyOf": [
|
|
182
|
+
{
|
|
183
|
+
"type": "STRING",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"format": "float",
|
|
187
|
+
"type": "NUMBER",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"type": "BOOLEAN",
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
}
|
|
194
|
+
`)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should create a CallableTool', async () => {
|
|
198
|
+
const weatherTool = tool({
|
|
199
|
+
description: 'Get weather',
|
|
200
|
+
inputSchema: z.object({
|
|
201
|
+
location: z.string(),
|
|
202
|
+
}),
|
|
203
|
+
execute: async ({ location }) => ({
|
|
204
|
+
temperature: 72,
|
|
205
|
+
location,
|
|
206
|
+
}),
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const callableTool = aiToolToCallableTool(weatherTool, 'weather')
|
|
210
|
+
|
|
211
|
+
// Test tool() method
|
|
212
|
+
const genAITool = await callableTool.tool()
|
|
213
|
+
expect(genAITool.functionDeclarations).toMatchInlineSnapshot(`
|
|
214
|
+
[
|
|
215
|
+
{
|
|
216
|
+
"description": "Get weather",
|
|
217
|
+
"name": "weather",
|
|
218
|
+
"parameters": {
|
|
219
|
+
"properties": {
|
|
220
|
+
"location": {
|
|
221
|
+
"type": "STRING",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
"required": [
|
|
225
|
+
"location",
|
|
226
|
+
],
|
|
227
|
+
"type": "OBJECT",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
`)
|
|
232
|
+
|
|
233
|
+
// Test callTool() method
|
|
234
|
+
const functionCall: FunctionCall = {
|
|
235
|
+
id: 'call_123',
|
|
236
|
+
name: 'weather',
|
|
237
|
+
args: { location: 'San Francisco' },
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const parts = await callableTool.callTool([functionCall])
|
|
241
|
+
expect(parts).toMatchInlineSnapshot(`
|
|
242
|
+
[
|
|
243
|
+
{
|
|
244
|
+
"functionResponse": {
|
|
245
|
+
"id": "call_123",
|
|
246
|
+
"name": "weather",
|
|
247
|
+
"response": {
|
|
248
|
+
"output": {
|
|
249
|
+
"location": "San Francisco",
|
|
250
|
+
"temperature": 72,
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
`)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should handle tool execution errors', async () => {
|
|
260
|
+
const errorTool = tool({
|
|
261
|
+
description: 'Tool that throws',
|
|
262
|
+
inputSchema: z.object({
|
|
263
|
+
trigger: z.boolean(),
|
|
264
|
+
}),
|
|
265
|
+
execute: async ({ trigger }) => {
|
|
266
|
+
if (trigger) {
|
|
267
|
+
throw new Error('Tool execution failed')
|
|
268
|
+
}
|
|
269
|
+
return { success: true }
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const callableTool = aiToolToCallableTool(errorTool, 'error_tool')
|
|
274
|
+
|
|
275
|
+
const functionCall: FunctionCall = {
|
|
276
|
+
id: 'call_error',
|
|
277
|
+
name: 'error_tool',
|
|
278
|
+
args: { trigger: true },
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const parts = await callableTool.callTool([functionCall])
|
|
282
|
+
expect(parts).toMatchInlineSnapshot(`
|
|
283
|
+
[
|
|
284
|
+
{
|
|
285
|
+
"functionResponse": {
|
|
286
|
+
"id": "call_error",
|
|
287
|
+
"name": "error_tool",
|
|
288
|
+
"response": {
|
|
289
|
+
"error": "Tool execution failed",
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
]
|
|
294
|
+
`)
|
|
295
|
+
})
|
|
296
|
+
})
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// Tool definition to Google GenAI tool converter.
|
|
2
|
+
// Transforms Kimaki's minimal Tool definitions into Google GenAI CallableTool format
|
|
3
|
+
// for use with Gemini's function calling in the voice assistant.
|
|
4
|
+
|
|
5
|
+
import type { AnyTool } from './ai-tool.js'
|
|
6
|
+
import type {
|
|
7
|
+
FunctionDeclaration,
|
|
8
|
+
Schema,
|
|
9
|
+
Tool as GenAITool,
|
|
10
|
+
CallableTool,
|
|
11
|
+
FunctionCall,
|
|
12
|
+
Part,
|
|
13
|
+
} from '@google/genai'
|
|
14
|
+
import { Type } from '@google/genai'
|
|
15
|
+
import { z, toJSONSchema } from 'zod'
|
|
16
|
+
import type { JSONSchema7, JSONSchema7Definition } from 'json-schema'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert JSON Schema to GenAI Schema format
|
|
20
|
+
* Based on the actual implementation used by the GenAI package:
|
|
21
|
+
* https://github.com/googleapis/js-genai/blob/027f09db662ce6b30f737b10b4d2efcb4282a9b6/src/_transformers.ts#L294
|
|
22
|
+
*/
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === 'object' && value !== null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function jsonSchemaToGenAISchema(jsonSchema: JSONSchema7Definition): Schema {
|
|
28
|
+
const schema: Schema = {}
|
|
29
|
+
|
|
30
|
+
if (typeof jsonSchema === 'boolean') {
|
|
31
|
+
return schema
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const jsonSchemaType: string | undefined = (() => {
|
|
35
|
+
if (!jsonSchema.type) {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
if (typeof jsonSchema.type === 'string') {
|
|
39
|
+
return jsonSchema.type
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(jsonSchema.type)) {
|
|
42
|
+
return jsonSchema.type.find((t) => t !== 'null') || jsonSchema.type[0]
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
})()
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(jsonSchema.type) && jsonSchema.type.includes('null')) {
|
|
48
|
+
schema.nullable = true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (jsonSchemaType) {
|
|
52
|
+
switch (jsonSchemaType) {
|
|
53
|
+
case 'string':
|
|
54
|
+
schema.type = Type.STRING
|
|
55
|
+
break
|
|
56
|
+
case 'number':
|
|
57
|
+
schema.type = Type.NUMBER
|
|
58
|
+
schema.format =
|
|
59
|
+
typeof jsonSchema.format === 'string' ? jsonSchema.format : 'float'
|
|
60
|
+
break
|
|
61
|
+
case 'integer':
|
|
62
|
+
schema.type = Type.INTEGER
|
|
63
|
+
schema.format =
|
|
64
|
+
typeof jsonSchema.format === 'string' ? jsonSchema.format : 'int32'
|
|
65
|
+
break
|
|
66
|
+
case 'boolean':
|
|
67
|
+
schema.type = Type.BOOLEAN
|
|
68
|
+
break
|
|
69
|
+
case 'array': {
|
|
70
|
+
schema.type = Type.ARRAY
|
|
71
|
+
const itemsSchema: JSONSchema7Definition | undefined = (() => {
|
|
72
|
+
if (!jsonSchema.items) {
|
|
73
|
+
return undefined
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(jsonSchema.items)) {
|
|
76
|
+
return jsonSchema.items[0]
|
|
77
|
+
}
|
|
78
|
+
return jsonSchema.items
|
|
79
|
+
})()
|
|
80
|
+
if (itemsSchema) {
|
|
81
|
+
schema.items = jsonSchemaToGenAISchema(itemsSchema)
|
|
82
|
+
}
|
|
83
|
+
if (typeof jsonSchema.minItems === 'number') {
|
|
84
|
+
schema.minItems = String(jsonSchema.minItems)
|
|
85
|
+
}
|
|
86
|
+
if (typeof jsonSchema.maxItems === 'number') {
|
|
87
|
+
schema.maxItems = String(jsonSchema.maxItems)
|
|
88
|
+
}
|
|
89
|
+
break
|
|
90
|
+
}
|
|
91
|
+
case 'object':
|
|
92
|
+
schema.type = Type.OBJECT
|
|
93
|
+
if (jsonSchema.properties) {
|
|
94
|
+
schema.properties = Object.fromEntries(
|
|
95
|
+
Object.entries(jsonSchema.properties).map(([key, value]) => [
|
|
96
|
+
key,
|
|
97
|
+
jsonSchemaToGenAISchema(value),
|
|
98
|
+
]),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(jsonSchema.required)) {
|
|
102
|
+
schema.required = jsonSchema.required
|
|
103
|
+
}
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof jsonSchema.description === 'string') {
|
|
109
|
+
schema.description = jsonSchema.description
|
|
110
|
+
}
|
|
111
|
+
if (Array.isArray(jsonSchema.enum)) {
|
|
112
|
+
schema.enum = jsonSchema.enum.map((x) => String(x))
|
|
113
|
+
}
|
|
114
|
+
if ('default' in jsonSchema) {
|
|
115
|
+
schema.default = jsonSchema.default as unknown
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(jsonSchema.examples) && jsonSchema.examples.length > 0) {
|
|
118
|
+
schema.example = jsonSchema.examples[0] as unknown
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(jsonSchema.anyOf)) {
|
|
122
|
+
schema.anyOf = jsonSchema.anyOf.map((s) => jsonSchemaToGenAISchema(s))
|
|
123
|
+
} else if (Array.isArray(jsonSchema.oneOf)) {
|
|
124
|
+
schema.anyOf = jsonSchema.oneOf.map((s) => jsonSchemaToGenAISchema(s))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof jsonSchema.minimum === 'number') {
|
|
128
|
+
schema.minimum = jsonSchema.minimum
|
|
129
|
+
}
|
|
130
|
+
if (typeof jsonSchema.maximum === 'number') {
|
|
131
|
+
schema.maximum = jsonSchema.maximum
|
|
132
|
+
}
|
|
133
|
+
if (typeof jsonSchema.minLength === 'number') {
|
|
134
|
+
schema.minLength = String(jsonSchema.minLength)
|
|
135
|
+
}
|
|
136
|
+
if (typeof jsonSchema.maxLength === 'number') {
|
|
137
|
+
schema.maxLength = String(jsonSchema.maxLength)
|
|
138
|
+
}
|
|
139
|
+
if (typeof jsonSchema.pattern === 'string') {
|
|
140
|
+
schema.pattern = jsonSchema.pattern
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return schema
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert AI SDK Tool to GenAI FunctionDeclaration
|
|
148
|
+
*/
|
|
149
|
+
export function aiToolToGenAIFunction(tool: AnyTool): FunctionDeclaration {
|
|
150
|
+
// Extract the input schema - assume it's a Zod schema
|
|
151
|
+
const inputSchema = tool.inputSchema as z.ZodType<unknown>
|
|
152
|
+
|
|
153
|
+
// Get the tool name from the schema or generate one
|
|
154
|
+
let toolName = 'tool'
|
|
155
|
+
let jsonSchema: JSONSchema7 = {}
|
|
156
|
+
|
|
157
|
+
if (inputSchema) {
|
|
158
|
+
// Convert Zod schema to JSON Schema
|
|
159
|
+
jsonSchema = toJSONSchema(inputSchema) as JSONSchema7
|
|
160
|
+
|
|
161
|
+
// Extract name from Zod description if available
|
|
162
|
+
const description = inputSchema.description
|
|
163
|
+
if (description) {
|
|
164
|
+
const nameMatch = description.match(/name:\s*(\w+)/)
|
|
165
|
+
if (nameMatch) {
|
|
166
|
+
toolName = nameMatch[1] || ''
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Convert JSON Schema to GenAI Schema
|
|
172
|
+
const genAISchema = jsonSchemaToGenAISchema(jsonSchema)
|
|
173
|
+
|
|
174
|
+
// Create the FunctionDeclaration
|
|
175
|
+
const functionDeclaration: FunctionDeclaration = {
|
|
176
|
+
name: toolName,
|
|
177
|
+
description: tool.description || jsonSchema.description || 'Tool function',
|
|
178
|
+
parameters: genAISchema,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return functionDeclaration
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Convert AI SDK Tool to GenAI CallableTool
|
|
186
|
+
*/
|
|
187
|
+
export function aiToolToCallableTool(
|
|
188
|
+
tool: AnyTool,
|
|
189
|
+
name: string,
|
|
190
|
+
): CallableTool & { name: string } {
|
|
191
|
+
const toolName = name || 'tool'
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
name,
|
|
195
|
+
async tool(): Promise<GenAITool> {
|
|
196
|
+
const functionDeclaration = aiToolToGenAIFunction(tool)
|
|
197
|
+
if (name) {
|
|
198
|
+
functionDeclaration.name = name
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
functionDeclarations: [functionDeclaration],
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async callTool(functionCalls: FunctionCall[]): Promise<Part[]> {
|
|
207
|
+
const parts: Part[] = []
|
|
208
|
+
|
|
209
|
+
for (const functionCall of functionCalls) {
|
|
210
|
+
// Check if this function call matches our tool
|
|
211
|
+
if (
|
|
212
|
+
functionCall.name !== toolName &&
|
|
213
|
+
name &&
|
|
214
|
+
functionCall.name !== name
|
|
215
|
+
) {
|
|
216
|
+
continue
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Execute the tool if it has an execute function
|
|
220
|
+
if (tool.execute) {
|
|
221
|
+
try {
|
|
222
|
+
const args: unknown = isRecord(functionCall.args)
|
|
223
|
+
? functionCall.args
|
|
224
|
+
: {}
|
|
225
|
+
const result = await tool.execute(args, {
|
|
226
|
+
toolCallId: functionCall.id || '',
|
|
227
|
+
messages: [],
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// Convert the result to a Part
|
|
231
|
+
const part: Part = {
|
|
232
|
+
functionResponse: {
|
|
233
|
+
id: functionCall.id,
|
|
234
|
+
name: functionCall.name || toolName,
|
|
235
|
+
response: {
|
|
236
|
+
output: result,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
parts.push(part)
|
|
241
|
+
} catch (error) {
|
|
242
|
+
// Handle errors
|
|
243
|
+
const part: Part = {
|
|
244
|
+
functionResponse: {
|
|
245
|
+
id: functionCall.id,
|
|
246
|
+
name: functionCall.name || toolName,
|
|
247
|
+
response: {
|
|
248
|
+
error: error instanceof Error ? error.message : String(error),
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
parts.push(part)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return parts
|
|
258
|
+
},
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function extractSchemaFromTool(tool: AnyTool): JSONSchema7 {
|
|
263
|
+
const inputSchema = tool.inputSchema as z.ZodType<unknown>
|
|
264
|
+
|
|
265
|
+
if (!inputSchema) {
|
|
266
|
+
return {}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Convert Zod schema to JSON Schema
|
|
270
|
+
return toJSONSchema(inputSchema) as JSONSchema7
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Given an object of tools, creates an array of CallableTool
|
|
275
|
+
*/
|
|
276
|
+
export function callableToolsFromObject(
|
|
277
|
+
tools: Record<string, AnyTool>,
|
|
278
|
+
): Array<CallableTool & { name: string }> {
|
|
279
|
+
return Object.entries(tools).map(([name, tool]) =>
|
|
280
|
+
aiToolToCallableTool(tool, name),
|
|
281
|
+
)
|
|
282
|
+
}
|
package/src/ai-tool.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Minimal tool definition helper used by Kimaki.
|
|
2
|
+
// This replaces the Vercel AI SDK `tool()` helper so Kimaki can define typed
|
|
3
|
+
// tools (Zod input schema + execute) without depending on the full `ai` package.
|
|
4
|
+
|
|
5
|
+
import type { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
export type ToolExecuteOptions = {
|
|
8
|
+
toolCallId?: string
|
|
9
|
+
abortSignal?: AbortSignal
|
|
10
|
+
messages?: unknown[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type BivariantCallback<Args extends unknown[], Return> = {
|
|
14
|
+
bivarianceHack(...args: Args): Return
|
|
15
|
+
}['bivarianceHack']
|
|
16
|
+
|
|
17
|
+
export type Tool<Input, Output> = {
|
|
18
|
+
description?: string
|
|
19
|
+
inputSchema: z.ZodType<Input>
|
|
20
|
+
execute?: BivariantCallback<
|
|
21
|
+
[input: Input, options: ToolExecuteOptions],
|
|
22
|
+
Promise<Output> | Output
|
|
23
|
+
>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AnyTool = {
|
|
27
|
+
description?: string
|
|
28
|
+
inputSchema: z.ZodTypeAny
|
|
29
|
+
execute?: BivariantCallback<
|
|
30
|
+
[input: unknown, options: ToolExecuteOptions],
|
|
31
|
+
Promise<unknown> | unknown
|
|
32
|
+
>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function tool<Input, Output>(
|
|
36
|
+
definition: Tool<Input, Output>,
|
|
37
|
+
): Tool<Input, Output> {
|
|
38
|
+
return definition
|
|
39
|
+
}
|