gitclaw 0.3.0 → 0.4.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/LICENSE +21 -0
- package/README.md +54 -28
- package/dist/composio/adapter.d.ts +26 -0
- package/dist/composio/adapter.js +92 -0
- package/dist/composio/client.d.ts +39 -0
- package/dist/composio/client.js +170 -0
- package/dist/composio/index.d.ts +2 -0
- package/dist/composio/index.js +2 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.js +211 -0
- package/dist/exports.d.ts +2 -0
- package/dist/exports.js +1 -0
- package/dist/index.js +99 -7
- package/dist/learning/reinforcement.d.ts +11 -0
- package/dist/learning/reinforcement.js +91 -0
- package/dist/loader.js +34 -1
- package/dist/sdk.js +5 -1
- package/dist/skills.d.ts +5 -0
- package/dist/skills.js +58 -7
- package/dist/tools/capture-photo.d.ts +3 -0
- package/dist/tools/capture-photo.js +91 -0
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +12 -2
- package/dist/tools/read.js +4 -0
- package/dist/tools/shared.d.ts +20 -0
- package/dist/tools/shared.js +24 -0
- package/dist/tools/skill-learner.d.ts +3 -0
- package/dist/tools/skill-learner.js +358 -0
- package/dist/tools/task-tracker.d.ts +20 -0
- package/dist/tools/task-tracker.js +275 -0
- package/dist/tools/write.js +4 -0
- package/dist/voice/adapter.d.ts +97 -0
- package/dist/voice/adapter.js +30 -0
- package/dist/voice/chat-history.d.ts +8 -0
- package/dist/voice/chat-history.js +121 -0
- package/dist/voice/gemini-live.d.ts +20 -0
- package/dist/voice/gemini-live.js +279 -0
- package/dist/voice/index.d.ts +4 -0
- package/dist/voice/index.js +3 -0
- package/dist/voice/openai-realtime.d.ts +27 -0
- package/dist/voice/openai-realtime.js +291 -0
- package/dist/voice/server.d.ts +2 -0
- package/dist/voice/server.js +2319 -0
- package/dist/voice/ui.html +2556 -0
- package/package.json +21 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 GitClaw Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./gitclaw-logo.png" alt="GitClaw Logo" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
<p align="center">
|
|
2
6
|
<img src="https://img.shields.io/npm/v/gitclaw?style=flat-square&color=blue" alt="npm version" />
|
|
3
7
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square" alt="node version" />
|
|
@@ -37,49 +41,57 @@ Most agent frameworks treat configuration as code scattered across your applicat
|
|
|
37
41
|
|
|
38
42
|
Fork an agent. Branch a personality. `git log` your agent's memory. Diff its rules. This is **agents as repos**.
|
|
39
43
|
|
|
40
|
-
##
|
|
41
|
-
|
|
42
|
-
### CLI
|
|
44
|
+
## Install
|
|
43
45
|
|
|
44
46
|
```bash
|
|
45
47
|
npm install -g gitclaw
|
|
48
|
+
```
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
**Run your first agent in one line:**
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
```bash
|
|
55
|
+
export OPENAI_API_KEY="sk-..."
|
|
56
|
+
gitclaw --dir ~/my-project "Explain this project and suggest improvements"
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
That's it. Gitclaw auto-scaffolds everything on first run
|
|
56
|
-
- `git init` if not already a repo
|
|
57
|
-
- Creates `agent.yaml`, `SOUL.md`, `memory/MEMORY.md`
|
|
58
|
-
- Commits the scaffold
|
|
59
|
-
- Drops you into the REPL
|
|
59
|
+
That's it. Gitclaw auto-scaffolds everything on first run — `agent.yaml`, `SOUL.md`, `memory/` — and drops you into the agent.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Tools: cli, read, write, memory
|
|
68
|
-
→ List all files and explain the project
|
|
61
|
+
### Local Repo Mode
|
|
62
|
+
|
|
63
|
+
Clone a GitHub repo, run an agent on it, auto-commit and push to a session branch:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
gitclaw --repo https://github.com/org/repo --pat ghp_xxx "Fix the login bug"
|
|
69
67
|
```
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
Resume an existing session:
|
|
72
70
|
|
|
73
71
|
```bash
|
|
74
|
-
gitclaw
|
|
72
|
+
gitclaw --repo https://github.com/org/repo --pat ghp_xxx --session gitclaw/session-a1b2c3d4 "Continue"
|
|
75
73
|
```
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
Token can come from env instead of `--pat`:
|
|
78
76
|
|
|
79
77
|
```bash
|
|
80
|
-
|
|
78
|
+
export GITHUB_TOKEN=ghp_xxx
|
|
79
|
+
gitclaw --repo https://github.com/org/repo "Add unit tests"
|
|
81
80
|
```
|
|
82
81
|
|
|
82
|
+
### CLI Options
|
|
83
|
+
|
|
84
|
+
| Flag | Short | Description |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `--dir <path>` | `-d` | Agent directory (default: cwd) |
|
|
87
|
+
| `--repo <url>` | `-r` | GitHub repo URL to clone and work on |
|
|
88
|
+
| `--pat <token>` | | GitHub PAT (or set `GITHUB_TOKEN` / `GIT_TOKEN`) |
|
|
89
|
+
| `--session <branch>` | | Resume an existing session branch |
|
|
90
|
+
| `--model <provider:model>` | `-m` | Override model (e.g. `anthropic:claude-sonnet-4-5-20250929`) |
|
|
91
|
+
| `--sandbox` | `-s` | Run in sandbox VM |
|
|
92
|
+
| `--prompt <text>` | `-p` | Single-shot prompt (skip REPL) |
|
|
93
|
+
| `--env <name>` | `-e` | Environment config |
|
|
94
|
+
|
|
83
95
|
### SDK
|
|
84
96
|
|
|
85
97
|
```bash
|
|
@@ -87,7 +99,7 @@ npm install gitclaw
|
|
|
87
99
|
```
|
|
88
100
|
|
|
89
101
|
```typescript
|
|
90
|
-
import { query
|
|
102
|
+
import { query } from "gitclaw";
|
|
91
103
|
|
|
92
104
|
// Simple query
|
|
93
105
|
for await (const msg of query({
|
|
@@ -98,6 +110,18 @@ for await (const msg of query({
|
|
|
98
110
|
if (msg.type === "delta") process.stdout.write(msg.content);
|
|
99
111
|
if (msg.type === "assistant") console.log("\n\nDone.");
|
|
100
112
|
}
|
|
113
|
+
|
|
114
|
+
// Local repo mode via SDK
|
|
115
|
+
for await (const msg of query({
|
|
116
|
+
prompt: "Fix the login bug",
|
|
117
|
+
model: "openai:gpt-4o-mini",
|
|
118
|
+
repo: {
|
|
119
|
+
url: "https://github.com/org/repo",
|
|
120
|
+
token: process.env.GITHUB_TOKEN!,
|
|
121
|
+
},
|
|
122
|
+
})) {
|
|
123
|
+
if (msg.type === "delta") process.stdout.write(msg.content);
|
|
124
|
+
}
|
|
101
125
|
```
|
|
102
126
|
|
|
103
127
|
## SDK
|
|
@@ -209,6 +233,8 @@ for await (const msg of query({
|
|
|
209
233
|
| `replaceBuiltinTools` | `boolean` | Skip cli/read/write/memory |
|
|
210
234
|
| `allowedTools` | `string[]` | Tool name allowlist |
|
|
211
235
|
| `disallowedTools` | `string[]` | Tool name denylist |
|
|
236
|
+
| `repo` | `LocalRepoOptions` | Clone a GitHub repo and work on a session branch |
|
|
237
|
+
| `sandbox` | `SandboxOptions \| boolean` | Run in sandbox VM (mutually exclusive with `repo`) |
|
|
212
238
|
| `hooks` | `GCHooks` | Programmatic lifecycle hooks |
|
|
213
239
|
| `maxTurns` | `number` | Max agent turns |
|
|
214
240
|
| `abortController` | `AbortController` | Cancellation signal |
|
|
@@ -433,8 +459,8 @@ Audit logs are written to `.gitagent/audit.jsonl` with full tool invocation trac
|
|
|
433
459
|
|
|
434
460
|
## Contributing
|
|
435
461
|
|
|
436
|
-
Contributions are welcome! Please
|
|
462
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
437
463
|
|
|
438
464
|
## License
|
|
439
465
|
|
|
440
|
-
MIT
|
|
466
|
+
This project is licensed under the [MIT License](./LICENSE).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GCToolDefinition } from "../sdk-types.js";
|
|
2
|
+
import { type ComposioToolkit, type ComposioConnection } from "./client.js";
|
|
3
|
+
interface ComposioAdapterOptions {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
userId?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ComposioAdapter {
|
|
8
|
+
private client;
|
|
9
|
+
private userId;
|
|
10
|
+
private cachedTools;
|
|
11
|
+
private cacheExpiry;
|
|
12
|
+
private static CACHE_TTL;
|
|
13
|
+
constructor(opts: ComposioAdapterOptions);
|
|
14
|
+
getTools(): Promise<GCToolDefinition[]>;
|
|
15
|
+
getToolsForQuery(query: string, limit?: number): Promise<GCToolDefinition[]>;
|
|
16
|
+
getConnectedToolkitSlugs(): Promise<string[]>;
|
|
17
|
+
getToolkits(): Promise<ComposioToolkit[]>;
|
|
18
|
+
connect(toolkit: string, redirectUrl?: string): Promise<{
|
|
19
|
+
connectionId: string;
|
|
20
|
+
redirectUrl: string;
|
|
21
|
+
}>;
|
|
22
|
+
getConnections(): Promise<ComposioConnection[]>;
|
|
23
|
+
disconnect(connectionId: string): Promise<void>;
|
|
24
|
+
private toGCTool;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Converts Composio tools into GCToolDefinition[] for injection into query()
|
|
2
|
+
import { ComposioClient } from "./client.js";
|
|
3
|
+
export class ComposioAdapter {
|
|
4
|
+
client;
|
|
5
|
+
userId;
|
|
6
|
+
cachedTools = null;
|
|
7
|
+
cacheExpiry = 0;
|
|
8
|
+
static CACHE_TTL = 30_000; // 30s
|
|
9
|
+
constructor(opts) {
|
|
10
|
+
this.client = new ComposioClient(opts.apiKey);
|
|
11
|
+
this.userId = opts.userId ?? "default";
|
|
12
|
+
}
|
|
13
|
+
// Core — returns all tools for connected toolkits (cached)
|
|
14
|
+
async getTools() {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
if (this.cachedTools && now < this.cacheExpiry)
|
|
17
|
+
return this.cachedTools;
|
|
18
|
+
const connections = await this.client.listConnections(this.userId);
|
|
19
|
+
if (connections.length === 0)
|
|
20
|
+
return [];
|
|
21
|
+
// Deduplicate toolkit slugs
|
|
22
|
+
const slugs = [...new Set(connections.map((c) => c.toolkitSlug))];
|
|
23
|
+
// Fetch tools for each connected toolkit in parallel
|
|
24
|
+
const toolsBySlug = await Promise.all(slugs.map((slug) => this.client.listTools(slug).catch(() => [])));
|
|
25
|
+
const tools = [];
|
|
26
|
+
for (const toolGroup of toolsBySlug) {
|
|
27
|
+
for (const t of toolGroup) {
|
|
28
|
+
tools.push(this.toGCTool(t));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
this.cachedTools = tools;
|
|
32
|
+
this.cacheExpiry = now + ComposioAdapter.CACHE_TTL;
|
|
33
|
+
return tools;
|
|
34
|
+
}
|
|
35
|
+
// Dynamically fetch only the relevant tools for a user query (semantic search)
|
|
36
|
+
async getToolsForQuery(query, limit = 15) {
|
|
37
|
+
const connections = await this.client.listConnections(this.userId);
|
|
38
|
+
if (connections.length === 0)
|
|
39
|
+
return [];
|
|
40
|
+
const slugs = [...new Set(connections.map((c) => c.toolkitSlug))];
|
|
41
|
+
const tools = await this.client.searchTools(query, slugs, limit);
|
|
42
|
+
// Sort: direct-action tools first (SEND, CREATE, LIST), drafts last
|
|
43
|
+
tools.sort((a, b) => {
|
|
44
|
+
const aIsDraft = a.slug.includes("DRAFT");
|
|
45
|
+
const bIsDraft = b.slug.includes("DRAFT");
|
|
46
|
+
if (aIsDraft !== bIsDraft)
|
|
47
|
+
return aIsDraft ? 1 : -1;
|
|
48
|
+
return 0;
|
|
49
|
+
});
|
|
50
|
+
return tools.map((t) => this.toGCTool(t));
|
|
51
|
+
}
|
|
52
|
+
// Returns deduplicated slugs of all connected toolkits
|
|
53
|
+
async getConnectedToolkitSlugs() {
|
|
54
|
+
const connections = await this.client.listConnections(this.userId);
|
|
55
|
+
return [...new Set(connections.map((c) => c.toolkitSlug))];
|
|
56
|
+
}
|
|
57
|
+
// Management endpoints — proxied for server routes
|
|
58
|
+
async getToolkits() {
|
|
59
|
+
return this.client.listToolkits(this.userId);
|
|
60
|
+
}
|
|
61
|
+
async connect(toolkit, redirectUrl) {
|
|
62
|
+
return this.client.initiateConnection(toolkit, this.userId, redirectUrl);
|
|
63
|
+
}
|
|
64
|
+
async getConnections() {
|
|
65
|
+
return this.client.listConnections(this.userId);
|
|
66
|
+
}
|
|
67
|
+
async disconnect(connectionId) {
|
|
68
|
+
await this.client.deleteConnection(connectionId);
|
|
69
|
+
// Invalidate cache so tools refresh on next query
|
|
70
|
+
this.cachedTools = null;
|
|
71
|
+
}
|
|
72
|
+
// ── Private ────────────────────────────────────────────────────────
|
|
73
|
+
toGCTool(t) {
|
|
74
|
+
const safeName = `composio_${t.toolkitSlug}_${t.slug}`.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
75
|
+
let description = `[Composio/${t.toolkitSlug}] ${t.description}`;
|
|
76
|
+
if (t.slug.includes("SEND_EMAIL")) {
|
|
77
|
+
description += " — USE THIS to send emails directly.";
|
|
78
|
+
}
|
|
79
|
+
else if (t.slug.includes("CREATE_EMAIL_DRAFT")) {
|
|
80
|
+
description += " — Only use when the user explicitly asks for a draft.";
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
name: safeName,
|
|
84
|
+
description,
|
|
85
|
+
inputSchema: t.parameters,
|
|
86
|
+
handler: async (args) => {
|
|
87
|
+
const result = await this.client.executeTool(t.slug, this.userId, args);
|
|
88
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ComposioToolkit {
|
|
2
|
+
slug: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
logo: string;
|
|
6
|
+
authSchemes: string[];
|
|
7
|
+
noAuth: boolean;
|
|
8
|
+
connected: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ComposioConnection {
|
|
11
|
+
id: string;
|
|
12
|
+
toolkitSlug: string;
|
|
13
|
+
status: string;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ComposioTool {
|
|
17
|
+
name: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
description: string;
|
|
20
|
+
toolkitSlug: string;
|
|
21
|
+
parameters: Record<string, any>;
|
|
22
|
+
}
|
|
23
|
+
export declare class ComposioClient {
|
|
24
|
+
private apiKey;
|
|
25
|
+
private authConfigCache;
|
|
26
|
+
constructor(apiKey: string);
|
|
27
|
+
listToolkits(userId?: string): Promise<ComposioToolkit[]>;
|
|
28
|
+
searchTools(query: string, toolkitSlugs?: string[], limit?: number): Promise<ComposioTool[]>;
|
|
29
|
+
listTools(toolkitSlug: string): Promise<ComposioTool[]>;
|
|
30
|
+
getOrCreateAuthConfig(toolkitSlug: string): Promise<string>;
|
|
31
|
+
initiateConnection(toolkitSlug: string, userId: string, redirectUrl?: string): Promise<{
|
|
32
|
+
connectionId: string;
|
|
33
|
+
redirectUrl: string;
|
|
34
|
+
}>;
|
|
35
|
+
listConnections(userId: string): Promise<ComposioConnection[]>;
|
|
36
|
+
deleteConnection(id: string): Promise<void>;
|
|
37
|
+
executeTool(toolSlug: string, userId: string, params: Record<string, any>, connectedAccountId?: string): Promise<any>;
|
|
38
|
+
private request;
|
|
39
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// Composio REST API v3 client — zero dependencies, uses native fetch()
|
|
2
|
+
const BASE_URL = "https://backend.composio.dev/api/v3";
|
|
3
|
+
// ── Client ───────────────────────────────────────────────────────────
|
|
4
|
+
export class ComposioClient {
|
|
5
|
+
apiKey;
|
|
6
|
+
// Cache auth config IDs so we don't recreate them every connect
|
|
7
|
+
authConfigCache = new Map();
|
|
8
|
+
constructor(apiKey) {
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
}
|
|
11
|
+
// List available toolkits, optionally merging connection status for a user
|
|
12
|
+
async listToolkits(userId) {
|
|
13
|
+
const resp = await this.request("GET", "/toolkits");
|
|
14
|
+
const toolkits = Array.isArray(resp) ? resp : (resp.items ?? resp.toolkits ?? []);
|
|
15
|
+
let connectedSlugs = new Set();
|
|
16
|
+
if (userId) {
|
|
17
|
+
try {
|
|
18
|
+
const conns = await this.listConnections(userId);
|
|
19
|
+
connectedSlugs = new Set(conns.map((c) => c.toolkitSlug));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// If connections fail, just show all as disconnected
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return toolkits.map((tk) => ({
|
|
26
|
+
slug: tk.slug ?? "",
|
|
27
|
+
name: tk.name ?? tk.slug ?? "",
|
|
28
|
+
description: tk.meta?.description ?? tk.description ?? "",
|
|
29
|
+
logo: tk.meta?.logo ?? tk.logo ?? "",
|
|
30
|
+
authSchemes: tk.auth_schemes ?? [],
|
|
31
|
+
noAuth: tk.no_auth ?? false,
|
|
32
|
+
connected: connectedSlugs.has(tk.slug ?? ""),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
// Search tools across connected toolkits by natural language query
|
|
36
|
+
// Makes parallel per-toolkit requests since the API doesn't support comma-separated toolkit_slug with query
|
|
37
|
+
async searchTools(query, toolkitSlugs, limit = 10) {
|
|
38
|
+
const mapTool = (t) => ({
|
|
39
|
+
name: t.name ?? t.enum ?? "",
|
|
40
|
+
slug: t.slug ?? t.enum ?? t.name ?? "",
|
|
41
|
+
description: t.description ?? "",
|
|
42
|
+
toolkitSlug: t.toolkit?.slug ?? t.toolkit_slug ?? "",
|
|
43
|
+
parameters: t.input_parameters ?? t.parameters ?? t.inputParameters ?? {},
|
|
44
|
+
});
|
|
45
|
+
if (!toolkitSlugs?.length) {
|
|
46
|
+
const params = new URLSearchParams({ query, limit: String(limit) });
|
|
47
|
+
const resp = await this.request("GET", `/tools?${params}`);
|
|
48
|
+
const tools = Array.isArray(resp) ? resp : (resp.items ?? resp.tools ?? []);
|
|
49
|
+
return tools.map(mapTool);
|
|
50
|
+
}
|
|
51
|
+
// Parallel per-toolkit search
|
|
52
|
+
const perToolkit = await Promise.all(toolkitSlugs.map(async (slug) => {
|
|
53
|
+
try {
|
|
54
|
+
const params = new URLSearchParams({ query, toolkit_slug: slug, limit: String(limit) });
|
|
55
|
+
const resp = await this.request("GET", `/tools?${params}`);
|
|
56
|
+
const tools = Array.isArray(resp) ? resp : (resp.items ?? resp.tools ?? []);
|
|
57
|
+
return tools.map(mapTool);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
return perToolkit.flat().slice(0, limit);
|
|
64
|
+
}
|
|
65
|
+
// List tools for a specific toolkit
|
|
66
|
+
async listTools(toolkitSlug) {
|
|
67
|
+
const resp = await this.request("GET", `/tools?toolkit_slug=${encodeURIComponent(toolkitSlug)}`);
|
|
68
|
+
const tools = Array.isArray(resp) ? resp : (resp.items ?? resp.tools ?? []);
|
|
69
|
+
return tools.map((t) => ({
|
|
70
|
+
name: t.name ?? t.enum ?? "",
|
|
71
|
+
slug: t.slug ?? t.enum ?? t.name ?? "",
|
|
72
|
+
description: t.description ?? "",
|
|
73
|
+
toolkitSlug,
|
|
74
|
+
parameters: t.input_parameters ?? t.parameters ?? t.inputParameters ?? {},
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
// Get or create an auth config for a toolkit (needed before creating a connection)
|
|
78
|
+
async getOrCreateAuthConfig(toolkitSlug) {
|
|
79
|
+
// Check cache first
|
|
80
|
+
const cached = this.authConfigCache.get(toolkitSlug);
|
|
81
|
+
if (cached)
|
|
82
|
+
return cached;
|
|
83
|
+
// Check if one already exists
|
|
84
|
+
const existing = await this.request("GET", `/auth_configs?toolkit_slug=${encodeURIComponent(toolkitSlug)}`);
|
|
85
|
+
const items = existing.items ?? [];
|
|
86
|
+
if (items.length > 0) {
|
|
87
|
+
const id = items[0].id ?? items[0].auth_config?.id;
|
|
88
|
+
if (id) {
|
|
89
|
+
this.authConfigCache.set(toolkitSlug, id);
|
|
90
|
+
return id;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Create a new one with Composio-managed auth
|
|
94
|
+
const created = await this.request("POST", "/auth_configs", {
|
|
95
|
+
toolkit: { slug: toolkitSlug },
|
|
96
|
+
auth_scheme: "OAUTH2",
|
|
97
|
+
use_composio_auth: true,
|
|
98
|
+
});
|
|
99
|
+
const id = created.auth_config?.id ?? created.id ?? "";
|
|
100
|
+
if (id)
|
|
101
|
+
this.authConfigCache.set(toolkitSlug, id);
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
// Start OAuth connection flow (two-step: ensure auth config, then create connection)
|
|
105
|
+
async initiateConnection(toolkitSlug, userId, redirectUrl) {
|
|
106
|
+
const authConfigId = await this.getOrCreateAuthConfig(toolkitSlug);
|
|
107
|
+
if (!authConfigId) {
|
|
108
|
+
throw new Error(`Failed to get auth config for toolkit: ${toolkitSlug}`);
|
|
109
|
+
}
|
|
110
|
+
const body = {
|
|
111
|
+
auth_config: { id: authConfigId },
|
|
112
|
+
connection: {
|
|
113
|
+
user_id: userId,
|
|
114
|
+
...(redirectUrl ? { callback_url: redirectUrl } : {}),
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
const resp = await this.request("POST", "/connected_accounts", body);
|
|
118
|
+
return {
|
|
119
|
+
connectionId: resp.id ?? "",
|
|
120
|
+
redirectUrl: resp.redirect_url ?? resp.redirect_uri ?? resp.redirectUrl ?? resp.redirectUri ?? "",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// List active connections for a user
|
|
124
|
+
async listConnections(userId) {
|
|
125
|
+
const resp = await this.request("GET", `/connected_accounts?user_ids=${encodeURIComponent(userId)}&statuses=ACTIVE`);
|
|
126
|
+
const items = Array.isArray(resp) ? resp : (resp.items ?? resp.connections ?? []);
|
|
127
|
+
return items.map((c) => ({
|
|
128
|
+
id: c.id ?? "",
|
|
129
|
+
toolkitSlug: c.toolkit?.slug ?? c.toolkit_slug ?? c.appUniqueId ?? c.integrationId ?? "",
|
|
130
|
+
status: c.status ?? "ACTIVE",
|
|
131
|
+
createdAt: c.createdAt ?? c.created_at ?? "",
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
// Delete a connection
|
|
135
|
+
async deleteConnection(id) {
|
|
136
|
+
await this.request("DELETE", `/connected_accounts/${encodeURIComponent(id)}`);
|
|
137
|
+
}
|
|
138
|
+
// Execute a tool action
|
|
139
|
+
async executeTool(toolSlug, userId, params, connectedAccountId) {
|
|
140
|
+
const body = {
|
|
141
|
+
arguments: params,
|
|
142
|
+
user_id: userId,
|
|
143
|
+
};
|
|
144
|
+
if (connectedAccountId)
|
|
145
|
+
body.connected_account_id = connectedAccountId;
|
|
146
|
+
return this.request("POST", `/tools/execute/${encodeURIComponent(toolSlug)}`, body);
|
|
147
|
+
}
|
|
148
|
+
// ── Private ────────────────────────────────────────────────────────
|
|
149
|
+
async request(method, path, body) {
|
|
150
|
+
const url = `${BASE_URL}${path}`;
|
|
151
|
+
const headers = {
|
|
152
|
+
"x-api-key": this.apiKey,
|
|
153
|
+
"Accept": "application/json",
|
|
154
|
+
};
|
|
155
|
+
if (body)
|
|
156
|
+
headers["Content-Type"] = "application/json";
|
|
157
|
+
const resp = await fetch(url, {
|
|
158
|
+
method,
|
|
159
|
+
headers,
|
|
160
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
161
|
+
});
|
|
162
|
+
if (!resp.ok) {
|
|
163
|
+
const text = await resp.text().catch(() => "");
|
|
164
|
+
throw new Error(`Composio API ${method} ${path} failed (${resp.status}): ${text}`);
|
|
165
|
+
}
|
|
166
|
+
if (resp.status === 204)
|
|
167
|
+
return undefined;
|
|
168
|
+
return resp.json();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface ContextSnapshot {
|
|
2
|
+
memory: string;
|
|
3
|
+
summary: string;
|
|
4
|
+
recentChat: string;
|
|
5
|
+
recentMood: string;
|
|
6
|
+
}
|
|
7
|
+
/** Read MEMORY.md + chat-summary + recent chat, returns raw content */
|
|
8
|
+
export declare function getContextSnapshot(agentDir: string, branch: string): Promise<ContextSnapshot>;
|
|
9
|
+
/**
|
|
10
|
+
* Returns context string for voice LLM system instructions.
|
|
11
|
+
* Includes: memory + conversation summary + recent chat history.
|
|
12
|
+
* Recent chat is critical — it survives page refreshes so the voice LLM
|
|
13
|
+
* knows what just happened even when the WebSocket reconnects.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getVoiceContext(agentDir: string, branch: string): Promise<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns richer context for run_agent systemPromptSuffix.
|
|
18
|
+
* Includes: full memory + summary. Capped at ~2000 tokens.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getAgentContext(agentDir: string, branch: string): Promise<string>;
|