skimpyclaw 0.1.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.
Files changed (219) hide show
  1. package/README.md +230 -0
  2. package/dist/__tests__/agent.test.d.ts +1 -0
  3. package/dist/__tests__/agent.test.js +131 -0
  4. package/dist/__tests__/api.test.d.ts +1 -0
  5. package/dist/__tests__/api.test.js +1227 -0
  6. package/dist/__tests__/audit.test.d.ts +1 -0
  7. package/dist/__tests__/audit.test.js +122 -0
  8. package/dist/__tests__/cache.test.d.ts +1 -0
  9. package/dist/__tests__/cache.test.js +65 -0
  10. package/dist/__tests__/channels.test.d.ts +1 -0
  11. package/dist/__tests__/channels.test.js +85 -0
  12. package/dist/__tests__/cli.integration.test.d.ts +1 -0
  13. package/dist/__tests__/cli.integration.test.js +16 -0
  14. package/dist/__tests__/cli.test.d.ts +1 -0
  15. package/dist/__tests__/cli.test.js +230 -0
  16. package/dist/__tests__/code-agents-executor.test.d.ts +1 -0
  17. package/dist/__tests__/code-agents-executor.test.js +75 -0
  18. package/dist/__tests__/code-agents-orchestrator.test.d.ts +1 -0
  19. package/dist/__tests__/code-agents-orchestrator.test.js +149 -0
  20. package/dist/__tests__/code-agents-parser.test.d.ts +1 -0
  21. package/dist/__tests__/code-agents-parser.test.js +39 -0
  22. package/dist/__tests__/code-agents-utils.test.d.ts +1 -0
  23. package/dist/__tests__/code-agents-utils.test.js +41 -0
  24. package/dist/__tests__/config.test.d.ts +1 -0
  25. package/dist/__tests__/config.test.js +46 -0
  26. package/dist/__tests__/cron.test.d.ts +1 -0
  27. package/dist/__tests__/cron.test.js +66 -0
  28. package/dist/__tests__/dashboard-mode.test.d.ts +1 -0
  29. package/dist/__tests__/dashboard-mode.test.js +145 -0
  30. package/dist/__tests__/dashboard.test.d.ts +1 -0
  31. package/dist/__tests__/dashboard.test.js +43 -0
  32. package/dist/__tests__/doctor.formatters.test.d.ts +1 -0
  33. package/dist/__tests__/doctor.formatters.test.js +65 -0
  34. package/dist/__tests__/doctor.index.test.d.ts +1 -0
  35. package/dist/__tests__/doctor.index.test.js +48 -0
  36. package/dist/__tests__/doctor.runner.test.d.ts +1 -0
  37. package/dist/__tests__/doctor.runner.test.js +204 -0
  38. package/dist/__tests__/exec-approval.test.d.ts +1 -0
  39. package/dist/__tests__/exec-approval.test.js +323 -0
  40. package/dist/__tests__/file-lock.test.d.ts +1 -0
  41. package/dist/__tests__/file-lock.test.js +92 -0
  42. package/dist/__tests__/langfuse.test.d.ts +1 -0
  43. package/dist/__tests__/langfuse.test.js +40 -0
  44. package/dist/__tests__/model-selection.test.d.ts +1 -0
  45. package/dist/__tests__/model-selection.test.js +62 -0
  46. package/dist/__tests__/orchestrator.test.d.ts +1 -0
  47. package/dist/__tests__/orchestrator.test.js +425 -0
  48. package/dist/__tests__/providers-init.test.d.ts +1 -0
  49. package/dist/__tests__/providers-init.test.js +32 -0
  50. package/dist/__tests__/providers-routing.test.d.ts +1 -0
  51. package/dist/__tests__/providers-routing.test.js +25 -0
  52. package/dist/__tests__/providers-utils.test.d.ts +1 -0
  53. package/dist/__tests__/providers-utils.test.js +54 -0
  54. package/dist/__tests__/security.test.d.ts +1 -0
  55. package/dist/__tests__/security.test.js +22 -0
  56. package/dist/__tests__/sessions.test.d.ts +1 -0
  57. package/dist/__tests__/sessions.test.js +147 -0
  58. package/dist/__tests__/setup.test.d.ts +1 -0
  59. package/dist/__tests__/setup.test.js +114 -0
  60. package/dist/__tests__/skills.test.d.ts +1 -0
  61. package/dist/__tests__/skills.test.js +333 -0
  62. package/dist/__tests__/subagent.test.d.ts +1 -0
  63. package/dist/__tests__/subagent.test.js +240 -0
  64. package/dist/__tests__/telegram-utils.test.d.ts +1 -0
  65. package/dist/__tests__/telegram-utils.test.js +22 -0
  66. package/dist/__tests__/telegram.test.d.ts +1 -0
  67. package/dist/__tests__/telegram.test.js +42 -0
  68. package/dist/__tests__/token-efficiency.test.d.ts +1 -0
  69. package/dist/__tests__/token-efficiency.test.js +38 -0
  70. package/dist/__tests__/tool-guard.test.d.ts +1 -0
  71. package/dist/__tests__/tool-guard.test.js +105 -0
  72. package/dist/__tests__/tools.test.d.ts +1 -0
  73. package/dist/__tests__/tools.test.js +589 -0
  74. package/dist/__tests__/usage.test.d.ts +1 -0
  75. package/dist/__tests__/usage.test.js +197 -0
  76. package/dist/__tests__/voice.test.d.ts +1 -0
  77. package/dist/__tests__/voice.test.js +214 -0
  78. package/dist/agent.d.ts +24 -0
  79. package/dist/agent.js +269 -0
  80. package/dist/api.d.ts +3 -0
  81. package/dist/api.js +943 -0
  82. package/dist/audit.d.ts +26 -0
  83. package/dist/audit.js +121 -0
  84. package/dist/cache.d.ts +8 -0
  85. package/dist/cache.js +24 -0
  86. package/dist/channels/telegram/handlers.d.ts +41 -0
  87. package/dist/channels/telegram/handlers.js +498 -0
  88. package/dist/channels/telegram/index.d.ts +14 -0
  89. package/dist/channels/telegram/index.js +326 -0
  90. package/dist/channels/telegram/types.d.ts +26 -0
  91. package/dist/channels/telegram/types.js +31 -0
  92. package/dist/channels/telegram/utils.d.ts +25 -0
  93. package/dist/channels/telegram/utils.js +256 -0
  94. package/dist/channels.d.ts +11 -0
  95. package/dist/channels.js +118 -0
  96. package/dist/cli.d.ts +5 -0
  97. package/dist/cli.js +768 -0
  98. package/dist/code-agents/executor.d.ts +5 -0
  99. package/dist/code-agents/executor.js +463 -0
  100. package/dist/code-agents/index.d.ts +22 -0
  101. package/dist/code-agents/index.js +199 -0
  102. package/dist/code-agents/orchestrator.d.ts +23 -0
  103. package/dist/code-agents/orchestrator.js +403 -0
  104. package/dist/code-agents/parser.d.ts +21 -0
  105. package/dist/code-agents/parser.js +197 -0
  106. package/dist/code-agents/registry.d.ts +27 -0
  107. package/dist/code-agents/registry.js +147 -0
  108. package/dist/code-agents/types.d.ts +66 -0
  109. package/dist/code-agents/types.js +4 -0
  110. package/dist/code-agents/utils.d.ts +36 -0
  111. package/dist/code-agents/utils.js +236 -0
  112. package/dist/config.d.ts +19 -0
  113. package/dist/config.js +123 -0
  114. package/dist/cron.d.ts +49 -0
  115. package/dist/cron.js +400 -0
  116. package/dist/dashboard/assets/index-CZJCvMSN.js +65 -0
  117. package/dist/dashboard/assets/index-EAg6lqF5.css +1 -0
  118. package/dist/dashboard/favicon.svg +3 -0
  119. package/dist/dashboard/index.html +21 -0
  120. package/dist/dashboard-frontend.d.ts +7 -0
  121. package/dist/dashboard-frontend.js +86 -0
  122. package/dist/dashboard.d.ts +8 -0
  123. package/dist/dashboard.js +4071 -0
  124. package/dist/digests.d.ts +36 -0
  125. package/dist/digests.js +338 -0
  126. package/dist/discord.d.ts +8 -0
  127. package/dist/discord.js +828 -0
  128. package/dist/doctor/checks.d.ts +18 -0
  129. package/dist/doctor/checks.js +368 -0
  130. package/dist/doctor/formatters.d.ts +3 -0
  131. package/dist/doctor/formatters.js +44 -0
  132. package/dist/doctor/index.d.ts +8 -0
  133. package/dist/doctor/index.js +7 -0
  134. package/dist/doctor/runner.d.ts +3 -0
  135. package/dist/doctor/runner.js +109 -0
  136. package/dist/doctor/types.d.ts +20 -0
  137. package/dist/doctor/types.js +1 -0
  138. package/dist/exec-approval.d.ts +101 -0
  139. package/dist/exec-approval.js +432 -0
  140. package/dist/file-lock.d.ts +34 -0
  141. package/dist/file-lock.js +81 -0
  142. package/dist/gateway.d.ts +8 -0
  143. package/dist/gateway.js +114 -0
  144. package/dist/heartbeat.d.ts +4 -0
  145. package/dist/heartbeat.js +101 -0
  146. package/dist/index.d.ts +1 -0
  147. package/dist/index.js +75 -0
  148. package/dist/langfuse.d.ts +34 -0
  149. package/dist/langfuse.js +145 -0
  150. package/dist/mcp-context-a8c.d.ts +13 -0
  151. package/dist/mcp-context-a8c.js +34 -0
  152. package/dist/model-selection.d.ts +18 -0
  153. package/dist/model-selection.js +50 -0
  154. package/dist/orchestrator.d.ts +15 -0
  155. package/dist/orchestrator.js +676 -0
  156. package/dist/providers/anthropic.d.ts +7 -0
  157. package/dist/providers/anthropic.js +319 -0
  158. package/dist/providers/codex.d.ts +17 -0
  159. package/dist/providers/codex.js +508 -0
  160. package/dist/providers/content.d.ts +21 -0
  161. package/dist/providers/content.js +55 -0
  162. package/dist/providers/index.d.ts +13 -0
  163. package/dist/providers/index.js +138 -0
  164. package/dist/providers/observability.d.ts +19 -0
  165. package/dist/providers/observability.js +94 -0
  166. package/dist/providers/openai.d.ts +10 -0
  167. package/dist/providers/openai.js +310 -0
  168. package/dist/providers/tool-guard.d.ts +30 -0
  169. package/dist/providers/tool-guard.js +89 -0
  170. package/dist/providers/types.d.ts +34 -0
  171. package/dist/providers/types.js +2 -0
  172. package/dist/providers/utils.d.ts +65 -0
  173. package/dist/providers/utils.js +199 -0
  174. package/dist/security.d.ts +8 -0
  175. package/dist/security.js +113 -0
  176. package/dist/service.d.ts +8 -0
  177. package/dist/service.js +38 -0
  178. package/dist/sessions.d.ts +35 -0
  179. package/dist/sessions.js +142 -0
  180. package/dist/setup.d.ts +36 -0
  181. package/dist/setup.js +821 -0
  182. package/dist/skills-types.d.ts +65 -0
  183. package/dist/skills-types.js +2 -0
  184. package/dist/skills.d.ts +32 -0
  185. package/dist/skills.js +260 -0
  186. package/dist/subagent.d.ts +19 -0
  187. package/dist/subagent.js +376 -0
  188. package/dist/telegram.d.ts +2 -0
  189. package/dist/telegram.js +11 -0
  190. package/dist/tools/bash-tool.d.ts +3 -0
  191. package/dist/tools/bash-tool.js +59 -0
  192. package/dist/tools/browser-tool.d.ts +3 -0
  193. package/dist/tools/browser-tool.js +265 -0
  194. package/dist/tools/definitions.d.ts +432 -0
  195. package/dist/tools/definitions.js +181 -0
  196. package/dist/tools/execute-context.d.ts +26 -0
  197. package/dist/tools/execute-context.js +1 -0
  198. package/dist/tools/file-tools.d.ts +8 -0
  199. package/dist/tools/file-tools.js +67 -0
  200. package/dist/tools/path-utils.d.ts +1 -0
  201. package/dist/tools/path-utils.js +8 -0
  202. package/dist/tools.d.ts +24 -0
  203. package/dist/tools.js +281 -0
  204. package/dist/types.d.ts +259 -0
  205. package/dist/types.js +2 -0
  206. package/dist/usage.d.ts +76 -0
  207. package/dist/usage.js +150 -0
  208. package/dist/voice.d.ts +37 -0
  209. package/dist/voice.js +461 -0
  210. package/package.json +70 -0
  211. package/templates/AGENTS.md +38 -0
  212. package/templates/BOOT.md +23 -0
  213. package/templates/BOOTSTRAP.md +26 -0
  214. package/templates/HEARTBEAT.md +5 -0
  215. package/templates/IDENTITY.md +5 -0
  216. package/templates/MEMORY.md +24 -0
  217. package/templates/SOUL.md +92 -0
  218. package/templates/TOOLS.md +30 -0
  219. package/templates/USER.md +31 -0
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # SkimpyClaw 👙🦞
2
+
3
+ Lightweight personal AI assistant (~20k LOC). Runs locally. Telegram/Discord chat, scheduled routines, a web dashboard, and a tool-enabled agent — all in one tiny service.
4
+
5
+ ## Why SkimpyClaw vs OpenClaw
6
+
7
+ Both are personal AI assistants you run yourself. The difference is scope.
8
+
9
+ | | SkimpyClaw (~20k LOC) | OpenClaw (~700k LOC) |
10
+ | ------------------- | --------------------------------------- | -------------------------------------------------------- |
11
+ | **Channels** | Telegram, Discord | WhatsApp, Signal, iMessage, Slack, Teams, Matrix, + more |
12
+ | **Setup** | `skimpyclaw onboard` → done | Daemon + wizard + per-channel pairing |
13
+ | **Codebase** | Read it in an afternoon | Full platform with extensions, packages, native UI |
14
+ | **Model support** | Anthropic, OpenAI, Kimi, MiniMax, Codex | Same + more |
15
+ | **Release cadence** | Move fast, no stability guarantees | Stable / beta / dev channels |
16
+
17
+ Use SkimpyClaw if you live in Telegram or Discord, want to read and own every line, and don't need 13 channels. Use OpenClaw if you need to support multiple channels like WhatsApp, Signal, iMessage, or Slack — or want a more polished, maintained platform.
18
+
19
+ ## Features
20
+
21
+ - **Chat interface** — Telegram and Discord bots with persistent conversation history
22
+ - **Tool-enabled agent** — file read/write, bash, browser (Playwright), MCP tools via mcporter
23
+ - **Multi-modal support** — voice messages (STT/TTS), image analysis
24
+ - **Subagents** — model autonomously spawns coding/research subagents with retry + concurrency control
25
+ - **Code agents** — delegate coding tasks to Claude Code, Codex, or Kimi CLI with `code_with_agent` and `code_with_team`
26
+ - **Cron scheduler** — run agent prompts or shell scripts on a schedule
27
+ - **Web dashboard** — Preact/Vite SPA with status, cron, audit log, memory, templates, config editor, skills, approvals
28
+ - **Heartbeat** — periodic keep-alive with Telegram/Discord alerts
29
+ - **Skills** — domain-specific capabilities loaded from `~/.skimpyclaw/skills/`
30
+ - **Exec approval** — human-in-the-loop approval for sensitive bash commands (tier 2-3 risks)
31
+ - **Voice** — optional TTS/STT support (ElevenLabs, OpenAI, macOS `say`, local Whisper)
32
+ - **Observability** — optional Langfuse tracing per agent turn
33
+ - **Audit logging** — JSONL-based audit traces for all agent interactions
34
+ - **Multiple model providers** — Anthropic, OpenAI, Kimi, MiniMax, Codex (ChatGPT backend), any OpenAI-compatible API
35
+
36
+ ## Architecture
37
+
38
+ ```mermaid
39
+ flowchart LR
40
+ subgraph Channels
41
+ user["Telegram / Discord"]
42
+ browser["Browser"]
43
+ end
44
+
45
+ subgraph Gateway["Fastify :18790"]
46
+ dash["Dashboard"]
47
+ api["REST API"]
48
+ end
49
+
50
+ subgraph Core
51
+ agent["Agent Runtime"]
52
+ subagents["Subagent Pool"]
53
+ cron["Cron"]
54
+ hb["Heartbeat"]
55
+ audit["Audit Log"]
56
+ skills["Skills System"]
57
+ approvals["Exec Approval"]
58
+ end
59
+
60
+ user --> agent
61
+ browser --> dash --> api --> agent
62
+ cron --> agent
63
+ hb --> agent
64
+ agent --> subagents
65
+ agent --> audit
66
+ agent --> skills
67
+ agent --> approvals
68
+ agent --> models["Anthropic / OpenAI / Codex"]
69
+ agent --> mcp["MCP Servers"]
70
+ agent --> fs["~/.skimpyclaw"]
71
+ ```
72
+
73
+ See [docs/architecture.md](docs/architecture.md) for runtime flow and startup sequence diagrams.
74
+
75
+ ## Quick Start
76
+
77
+ **Install:**
78
+
79
+ ```bash
80
+ npm install -g skimpyclaw
81
+ ```
82
+
83
+ **Run onboarding:**
84
+
85
+ ```bash
86
+ skimpyclaw onboard
87
+ ```
88
+
89
+ Onboarding validates your Telegram token, provider auth, and creates:
90
+
91
+ - `~/.skimpyclaw/config.json`
92
+ - `~/.skimpyclaw/agents/main/*.md` (from templates)
93
+
94
+ **Start:**
95
+
96
+ ```bash
97
+ skimpyclaw start
98
+ ```
99
+
100
+ **Verify:**
101
+
102
+ ```bash
103
+ curl http://127.0.0.1:18790/health
104
+ ```
105
+
106
+ **Open dashboard:**
107
+
108
+ ```
109
+ http://127.0.0.1:18790/dashboard
110
+ ```
111
+
112
+ Bearer token is shown in startup logs.
113
+
114
+ ## Tech Stack
115
+
116
+ - **Backend:** TypeScript (ESM), Fastify, Vitest
117
+ - **Frontend:** Preact, Vite, TypeScript
118
+ - **Chat:** grammy (Telegram), discord.js (Discord)
119
+ - **Scheduling:** Croner
120
+ - **Browser:** Playwright
121
+ - **AI SDKs:** Anthropic SDK, OpenAI SDK
122
+ - **Observability:** Langfuse (optional)
123
+
124
+ ## Project Structure
125
+
126
+ ```
127
+ src/
128
+ index.ts # App entrypoint with logging setup
129
+ gateway.ts # Fastify server + top-level routes
130
+ agent.ts # Prompt assembly, model routing, tool loop, memory writes
131
+ tools.ts # Tool registry + dispatch
132
+ tools/ # Tool executors (bash, browser, file tools, path utils, execute context)
133
+ providers/ # Provider routing + provider implementations (anthropic/openai/codex)
134
+ code-agents/ # Background coding-agent runtime (executor/parser/orchestrator/registry)
135
+ channels/ # Channel adapters/utilities (telegram/discord)
136
+ subagent.ts # Background task dispatch: retry, concurrency, disk registry
137
+ file-lock.ts # In-memory file lock for concurrent subagent writes
138
+ audit.ts # Append-only audit log (trace/event model, JSONL storage)
139
+ cron.ts # Job scheduling + execution + cron logging
140
+ heartbeat.ts # Periodic health/attention checks
141
+ channels.ts # Active channel selection + proactive routing
142
+ telegram.ts # Telegram bot commands and message handling
143
+ discord.ts # Discord bot commands and message handling
144
+ voice.ts # Voice input/output (TTS/STT via providers)
145
+ digests.ts # Daily digest generation for cron job outputs
146
+ skills.ts # Skill loading, eligibility checks, and prompt injection
147
+ skills-types.ts # TypeScript types for skills system
148
+ exec-approval.ts # Human-in-the-loop exec approval flow with risk tiers
149
+ api.ts # Dashboard REST API under /api/dashboard/*
150
+ dashboard-frontend.ts # Dashboard static asset serving
151
+ security.ts # Auth, path validation, bash command blocklist, rate limiting
152
+ config.ts # Config loading and validation
153
+ types.ts # All TypeScript interfaces and types
154
+ setup.ts # Interactive setup wizard
155
+ langfuse.ts # Observability integration with cost tracking
156
+ usage.ts # Token usage tracking and aggregation
157
+ service.ts # Runtime service management
158
+ cli.ts # CLI command definitions
159
+ cache.ts # TTL cache utility
160
+ sessions.ts # Session persistence for chat history
161
+ doctor/ # Health check system
162
+ index.ts
163
+ checks.ts
164
+ formatters.ts
165
+ runner.ts
166
+ types.ts
167
+
168
+ web/dashboard/ # Preact/Vite dashboard frontend
169
+ src/
170
+ App.tsx
171
+ api/client.ts
172
+ components/
173
+ pages/ # Overview, Cron, Audit, Memory, Config, Skills, etc.
174
+
175
+ templates/ # Default template markdown files
176
+ SOUL.md
177
+ IDENTITY.md
178
+ USER.md
179
+ TOOLS.md
180
+ BOOT.md
181
+ HEARTBEAT.md
182
+ MEMORY.md
183
+ AGENTS.md
184
+ BOOTSTRAP.md
185
+
186
+ dist/ # Compiled output + built dashboard assets
187
+ ```
188
+
189
+ ## Documentation
190
+
191
+ | Doc | Contents |
192
+ | ---------------------------------------------- | ---------------------------------------------------------------- |
193
+ | [docs/architecture.md](docs/architecture.md) | Component diagram, runtime flow, startup sequence, source layout |
194
+ | [docs/configuration.md](docs/configuration.md) | Full config reference, all sections with examples |
195
+ | [docs/tools.md](docs/tools.md) | Built-in tools, browser tool, MCP integration, code agents |
196
+ | [docs/subagents.md](docs/subagents.md) | Subagent types, concurrency, file locking, flow diagram |
197
+ | [docs/dashboard.md](docs/dashboard.md) | Web dashboard, all HTTP endpoints + API routes |
198
+ | [docs/coding-agents.md](docs/coding-agents.md) | Coding-agent execution model and CLI backends |
199
+ | [docs/cli.md](docs/cli.md) | CLI commands, service management |
200
+ | [docs/chat-commands.md](docs/chat-commands.md) | Telegram/Discord bot commands |
201
+ | [docs/skills.md](docs/skills.md) | Skills system, built-in skills, creating custom skills |
202
+ | [docs/data-storage.md](docs/data-storage.md) | File layout, audit log format, security notes |
203
+ | [docs/setup-guide.md](docs/setup-guide.md) | Step-by-step installation and setup guide |
204
+ | [docs/troubleshooting.md](docs/troubleshooting.md) | Common issues and solutions |
205
+
206
+ ## Development
207
+
208
+ ```bash
209
+ # Install dependencies
210
+ pnpm install
211
+
212
+ # Run in development mode (hot reload)
213
+ pnpm dev
214
+
215
+ # Build TypeScript and dashboard
216
+ pnpm build
217
+
218
+ # Run tests
219
+ pnpm test
220
+
221
+ # Run full CI gate
222
+ pnpm ci
223
+
224
+ # Run doctor checks
225
+ pnpm run doctor
226
+ ```
227
+
228
+ ## License
229
+
230
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { toOpenAITools, buildSystemParam, addToolCacheBreakpoint, setUsingOAuth } from '../agent.js';
3
+ describe('toOpenAITools', () => {
4
+ it('converts Anthropic tool format to OpenAI function format', () => {
5
+ const anthropicTools = [
6
+ {
7
+ name: 'Read',
8
+ description: 'Read a file',
9
+ input_schema: {
10
+ type: 'object',
11
+ properties: {
12
+ file_path: { type: 'string', description: 'Path to file' },
13
+ },
14
+ required: ['file_path'],
15
+ },
16
+ },
17
+ ];
18
+ const result = toOpenAITools(anthropicTools);
19
+ expect(result).toHaveLength(1);
20
+ expect(result[0]).toEqual({
21
+ type: 'function',
22
+ function: {
23
+ name: 'Read',
24
+ description: 'Read a file',
25
+ parameters: {
26
+ type: 'object',
27
+ properties: {
28
+ file_path: { type: 'string', description: 'Path to file' },
29
+ },
30
+ required: ['file_path'],
31
+ },
32
+ },
33
+ });
34
+ });
35
+ it('converts multiple tools', () => {
36
+ const tools = [
37
+ { name: 'Read', description: 'Read', input_schema: { type: 'object', properties: {} } },
38
+ { name: 'Write', description: 'Write', input_schema: { type: 'object', properties: {} } },
39
+ { name: 'Bash', description: 'Bash', input_schema: { type: 'object', properties: {} } },
40
+ ];
41
+ const result = toOpenAITools(tools);
42
+ expect(result).toHaveLength(3);
43
+ expect(result.every((t) => t.type === 'function')).toBe(true);
44
+ expect(result.map((t) => t.function.name)).toEqual(['Read', 'Write', 'Bash']);
45
+ });
46
+ it('preserves input_schema as parameters unchanged', () => {
47
+ const schema = {
48
+ type: 'object',
49
+ properties: {
50
+ command: { type: 'string' },
51
+ cwd: { type: 'string' },
52
+ },
53
+ required: ['command'],
54
+ };
55
+ const result = toOpenAITools([{ name: 'Bash', description: 'Run command', input_schema: schema }]);
56
+ expect(result[0].function.parameters).toBe(schema); // same reference, not deep-copied
57
+ });
58
+ it('handles empty array', () => {
59
+ expect(toOpenAITools([])).toEqual([]);
60
+ });
61
+ });
62
+ describe('buildSystemParam', () => {
63
+ beforeEach(() => {
64
+ setUsingOAuth(false);
65
+ });
66
+ it('returns undefined for empty content', () => {
67
+ expect(buildSystemParam(undefined)).toBeUndefined();
68
+ expect(buildSystemParam(undefined, true)).toBeUndefined();
69
+ });
70
+ it('returns plain string when caching disabled and not OAuth', () => {
71
+ const result = buildSystemParam('Hello system');
72
+ expect(result).toBe('Hello system');
73
+ });
74
+ it('returns plain string when caching explicitly disabled', () => {
75
+ const result = buildSystemParam('Hello system', false);
76
+ expect(result).toBe('Hello system');
77
+ });
78
+ it('returns array with cache_control when caching enabled (API key mode)', () => {
79
+ const result = buildSystemParam('Hello system', true);
80
+ expect(Array.isArray(result)).toBe(true);
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0]).toEqual({
83
+ type: 'text',
84
+ text: 'Hello system',
85
+ cache_control: { type: 'ephemeral' },
86
+ });
87
+ });
88
+ it('returns 3-block array with cache_control on last block (OAuth mode)', () => {
89
+ setUsingOAuth(true);
90
+ const result = buildSystemParam('My prompt', true);
91
+ expect(Array.isArray(result)).toBe(true);
92
+ expect(result).toHaveLength(3);
93
+ // First two blocks: no cache_control
94
+ expect(result[0].cache_control).toBeUndefined();
95
+ expect(result[1].cache_control).toBeUndefined();
96
+ // Last block: has cache_control
97
+ expect(result[2].cache_control).toEqual({ type: 'ephemeral' });
98
+ expect(result[2].text).toBe('My prompt');
99
+ });
100
+ it('returns 3-block array without cache_control when caching disabled (OAuth mode)', () => {
101
+ setUsingOAuth(true);
102
+ const result = buildSystemParam('My prompt', false);
103
+ expect(Array.isArray(result)).toBe(true);
104
+ expect(result).toHaveLength(3);
105
+ expect(result[0].cache_control).toBeUndefined();
106
+ expect(result[1].cache_control).toBeUndefined();
107
+ expect(result[2].cache_control).toBeUndefined();
108
+ });
109
+ });
110
+ describe('addToolCacheBreakpoint', () => {
111
+ it('adds cache_control to the last tool definition', () => {
112
+ const tools = [
113
+ { name: 'Read', description: 'Read' },
114
+ { name: 'Write', description: 'Write' },
115
+ { name: 'Bash', description: 'Bash' },
116
+ ];
117
+ addToolCacheBreakpoint(tools);
118
+ expect(tools[0].cache_control).toBeUndefined();
119
+ expect(tools[1].cache_control).toBeUndefined();
120
+ expect(tools[2].cache_control).toEqual({ type: 'ephemeral' });
121
+ });
122
+ it('handles single tool', () => {
123
+ const tools = [{ name: 'Read', description: 'Read' }];
124
+ addToolCacheBreakpoint(tools);
125
+ expect(tools[0].cache_control).toEqual({ type: 'ephemeral' });
126
+ });
127
+ it('handles empty array without error', () => {
128
+ const tools = [];
129
+ expect(() => addToolCacheBreakpoint(tools)).not.toThrow();
130
+ });
131
+ });
@@ -0,0 +1 @@
1
+ export {};