@clanker-cleanroom/claude 0.2.5
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 +40 -0
- package/docker/claude.Dockerfile +32 -0
- package/lib/binding-C2kSQy-B.cjs +279 -0
- package/lib/binding-C2kSQy-B.cjs.map +1 -0
- package/lib/binding-DAPBfelH.mjs +226 -0
- package/lib/binding-DAPBfelH.mjs.map +1 -0
- package/lib/index.cjs +28 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +52 -0
- package/lib/index.d.mts +52 -0
- package/lib/index.mjs +16 -0
- package/lib/index.mjs.map +1 -0
- package/lib/register.cjs +6 -0
- package/lib/register.cjs.map +1 -0
- package/lib/register.d.cts +1 -0
- package/lib/register.d.mts +1 -0
- package/lib/register.mjs +8 -0
- package/lib/register.mjs.map +1 -0
- package/package.json +136 -0
- package/src/agent.ts +5 -0
- package/src/binding.ts +112 -0
- package/src/constants.ts +13 -0
- package/src/credentials.ts +61 -0
- package/src/index.ts +12 -0
- package/src/package-root.ts +8 -0
- package/src/register.ts +5 -0
- package/src/trajectory.ts +119 -0
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_binding = require("./binding-C2kSQy-B.cjs");
|
|
3
|
+
let clanker_cleanroom = require("clanker-cleanroom");
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
let node_url = require("node:url");
|
|
6
|
+
//#region src/agent.ts
|
|
7
|
+
const claudeAgent = clanker_cleanroom.Agent.fromBinding(require_binding.claudeBinding);
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/package-root.ts
|
|
10
|
+
/** Absolute path to this package root (parent of `src/` or `lib/`). */
|
|
11
|
+
const PACKAGE_ROOT = (0, node_url.fileURLToPath)(new URL("..", require("url").pathToFileURL(__filename).href));
|
|
12
|
+
/** Folder of `*.Dockerfile` files for this agent. */
|
|
13
|
+
const DOCKER_DIR = (0, node_path.join)(PACKAGE_ROOT, "docker");
|
|
14
|
+
//#endregion
|
|
15
|
+
exports.CLAUDE_API_KEY_ENV = require_binding.CLAUDE_API_KEY_ENV;
|
|
16
|
+
exports.CLAUDE_CONTAINER_CREDENTIALS_PATH = require_binding.CLAUDE_CONTAINER_CREDENTIALS_PATH;
|
|
17
|
+
exports.CLAUDE_IMAGE = require_binding.CLAUDE_IMAGE;
|
|
18
|
+
exports.CLAUDE_OAUTH_TOKEN_ENV = require_binding.CLAUDE_OAUTH_TOKEN_ENV;
|
|
19
|
+
exports.DOCKER_DIR = DOCKER_DIR;
|
|
20
|
+
exports.PACKAGE_ROOT = PACKAGE_ROOT;
|
|
21
|
+
exports.adaptClaudeEvents = require_binding.adaptClaudeEvents;
|
|
22
|
+
exports.claudeAgent = claudeAgent;
|
|
23
|
+
exports.claudeBinding = require_binding.claudeBinding;
|
|
24
|
+
exports.credentialsEnv = require_binding.credentialsEnv;
|
|
25
|
+
exports.defaultClaudeHostCredentialsFile = require_binding.defaultClaudeHostCredentialsFile;
|
|
26
|
+
exports.resolveClaudeCredentials = require_binding.resolveClaudeCredentials;
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Agent","claudeBinding","fileURLToPath","join"],"sources":["../src/agent.ts","../src/package-root.ts"],"sourcesContent":["import { Agent } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nexport const claudeAgent = Agent.fromBinding(claudeBinding);\n","import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to this package root (parent of `src/` or `lib/`). */\nexport const PACKAGE_ROOT = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** Folder of `*.Dockerfile` files for this agent. */\nexport const DOCKER_DIR = join(PACKAGE_ROOT, \"docker\");\n"],"mappings":";;;;;;AAIA,MAAa,cAAcA,kBAAAA,MAAM,YAAYC,gBAAAA,aAAa;;;;ACA1D,MAAa,gBAAA,GAAeC,SAAAA,cAAAA,CAAc,IAAI,IAAI,MAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAAqB,CAAC;;AAGxE,MAAa,cAAA,GAAaC,UAAAA,KAAAA,CAAK,cAAc,QAAQ"}
|
package/lib/index.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Agent, AgentBinding, TrajectoryEvent } from "clanker-cleanroom";
|
|
2
|
+
//#region src/constants.d.ts
|
|
3
|
+
declare const CLAUDE_IMAGE = "clanker-cleanroom/claude";
|
|
4
|
+
declare const CLAUDE_CONTAINER_CREDENTIALS_PATH = "/home/agent/.claude/.credentials.json";
|
|
5
|
+
/** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
|
|
6
|
+
declare const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
7
|
+
/** Anthropic API key (pay-as-you-go). */
|
|
8
|
+
declare const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
9
|
+
/** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
|
|
10
|
+
declare const defaultClaudeHostCredentialsFile: (home: string) => string;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/binding.d.ts
|
|
13
|
+
declare const claudeBinding: AgentBinding;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/credentials.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* How Claude Code authenticates inside the container. Env-backed kinds are forwarded
|
|
18
|
+
* to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
|
|
19
|
+
* See `constants.ts` for which host setups produce each.
|
|
20
|
+
*/
|
|
21
|
+
type ClaudeCredentials = {
|
|
22
|
+
kind: "oauth-token";
|
|
23
|
+
token: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "api-key";
|
|
26
|
+
apiKey: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "credentials-file";
|
|
29
|
+
file: string;
|
|
30
|
+
};
|
|
31
|
+
/** Env OAuth token, then env API key, then a readable host credentials file. */
|
|
32
|
+
declare function resolveClaudeCredentials(options?: {
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
home?: string;
|
|
35
|
+
}): Promise<ClaudeCredentials>;
|
|
36
|
+
/** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
|
|
37
|
+
declare function credentialsEnv(credentials: ClaudeCredentials): Record<string, string>;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/agent.d.ts
|
|
40
|
+
declare const claudeAgent: Agent;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/trajectory.d.ts
|
|
43
|
+
declare function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[];
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/package-root.d.ts
|
|
46
|
+
/** Absolute path to this package root (parent of `src/` or `lib/`). */
|
|
47
|
+
declare const PACKAGE_ROOT: string;
|
|
48
|
+
/** Folder of `*.Dockerfile` files for this agent. */
|
|
49
|
+
declare const DOCKER_DIR: string;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, type ClaudeCredentials, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
|
|
52
|
+
//# sourceMappingURL=index.d.cts.map
|
package/lib/index.d.mts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Agent, AgentBinding, TrajectoryEvent } from "clanker-cleanroom";
|
|
2
|
+
//#region src/constants.d.ts
|
|
3
|
+
declare const CLAUDE_IMAGE = "clanker-cleanroom/claude";
|
|
4
|
+
declare const CLAUDE_CONTAINER_CREDENTIALS_PATH = "/home/agent/.claude/.credentials.json";
|
|
5
|
+
/** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
|
|
6
|
+
declare const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
7
|
+
/** Anthropic API key (pay-as-you-go). */
|
|
8
|
+
declare const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
9
|
+
/** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
|
|
10
|
+
declare const defaultClaudeHostCredentialsFile: (home: string) => string;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/binding.d.ts
|
|
13
|
+
declare const claudeBinding: AgentBinding;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/credentials.d.ts
|
|
16
|
+
/**
|
|
17
|
+
* How Claude Code authenticates inside the container. Env-backed kinds are forwarded
|
|
18
|
+
* to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
|
|
19
|
+
* See `constants.ts` for which host setups produce each.
|
|
20
|
+
*/
|
|
21
|
+
type ClaudeCredentials = {
|
|
22
|
+
kind: "oauth-token";
|
|
23
|
+
token: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "api-key";
|
|
26
|
+
apiKey: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "credentials-file";
|
|
29
|
+
file: string;
|
|
30
|
+
};
|
|
31
|
+
/** Env OAuth token, then env API key, then a readable host credentials file. */
|
|
32
|
+
declare function resolveClaudeCredentials(options?: {
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
home?: string;
|
|
35
|
+
}): Promise<ClaudeCredentials>;
|
|
36
|
+
/** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
|
|
37
|
+
declare function credentialsEnv(credentials: ClaudeCredentials): Record<string, string>;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/agent.d.ts
|
|
40
|
+
declare const claudeAgent: Agent;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/trajectory.d.ts
|
|
43
|
+
declare function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[];
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/package-root.d.ts
|
|
46
|
+
/** Absolute path to this package root (parent of `src/` or `lib/`). */
|
|
47
|
+
declare const PACKAGE_ROOT: string;
|
|
48
|
+
/** Folder of `*.Dockerfile` files for this agent. */
|
|
49
|
+
declare const DOCKER_DIR: string;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, type ClaudeCredentials, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
|
|
52
|
+
//# sourceMappingURL=index.d.mts.map
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { a as CLAUDE_API_KEY_ENV, c as CLAUDE_OAUTH_TOKEN_ENV, i as resolveClaudeCredentials, l as defaultClaudeHostCredentialsFile, n as adaptClaudeEvents, o as CLAUDE_CONTAINER_CREDENTIALS_PATH, r as credentialsEnv, s as CLAUDE_IMAGE, t as claudeBinding } from "./binding-DAPBfelH.mjs";
|
|
2
|
+
import { Agent } from "clanker-cleanroom";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
//#region src/agent.ts
|
|
6
|
+
const claudeAgent = Agent.fromBinding(claudeBinding);
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/package-root.ts
|
|
9
|
+
/** Absolute path to this package root (parent of `src/` or `lib/`). */
|
|
10
|
+
const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
11
|
+
/** Folder of `*.Dockerfile` files for this agent. */
|
|
12
|
+
const DOCKER_DIR = join(PACKAGE_ROOT, "docker");
|
|
13
|
+
//#endregion
|
|
14
|
+
export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/agent.ts","../src/package-root.ts"],"sourcesContent":["import { Agent } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nexport const claudeAgent = Agent.fromBinding(claudeBinding);\n","import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to this package root (parent of `src/` or `lib/`). */\nexport const PACKAGE_ROOT = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** Folder of `*.Dockerfile` files for this agent. */\nexport const DOCKER_DIR = join(PACKAGE_ROOT, \"docker\");\n"],"mappings":";;;;;AAIA,MAAa,cAAc,MAAM,YAAY,aAAa;;;;ACA1D,MAAa,eAAe,cAAc,IAAI,IAAI,MAAM,YAAY,GAAG,CAAC;;AAGxE,MAAa,aAAa,KAAK,cAAc,QAAQ"}
|
package/lib/register.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.cjs","names":["registerBinding","claudeBinding"],"sources":["../src/register.ts"],"sourcesContent":["import { registerBinding } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nregisterBinding(\"claude\", claudeBinding);\n"],"mappings":";;CAIAA,8BAAAA,CAAAA,CAAAA,gBAAAA,CAAgB,UAAUC,gBAAAA,aAAa"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/lib/register.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.mjs","names":[],"sources":["../src/register.ts"],"sourcesContent":["import { registerBinding } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nregisterBinding(\"claude\", claudeBinding);\n"],"mappings":";;;AAIA,gBAAgB,UAAU,aAAa"}
|
package/package.json
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clanker-cleanroom/claude",
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Claude Code agent binding for clanker-cleanroom",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"claude",
|
|
8
|
+
"claude-code",
|
|
9
|
+
"cleanroom",
|
|
10
|
+
"docker"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/improving/agent-gwt#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/improving/agent-gwt/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Eric Siebeneich <eric.siebeneich@improving.com> (https://github.com/improving)",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/improving/agent-gwt.git",
|
|
21
|
+
"directory": "packages/clanker-cleanroom-claude"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"src",
|
|
26
|
+
"docker",
|
|
27
|
+
"!**/*.spec.ts"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./lib/index.cjs",
|
|
31
|
+
"module": "./lib/index.mjs",
|
|
32
|
+
"types": "./lib/index.d.cts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./lib/index.d.mts",
|
|
37
|
+
"default": "./lib/index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./lib/index.d.cts",
|
|
41
|
+
"default": "./lib/index.cjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./register": {
|
|
45
|
+
"import": {
|
|
46
|
+
"types": "./lib/register.d.mts",
|
|
47
|
+
"default": "./lib/register.mjs"
|
|
48
|
+
},
|
|
49
|
+
"require": {
|
|
50
|
+
"types": "./lib/register.d.cts",
|
|
51
|
+
"default": "./lib/register.cjs"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"./package.json": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
},
|
|
59
|
+
"wireit": {
|
|
60
|
+
"build": {
|
|
61
|
+
"command": "vp pack",
|
|
62
|
+
"files": [
|
|
63
|
+
"src/**/*.ts",
|
|
64
|
+
"tsconfig.json",
|
|
65
|
+
"vite.config.ts",
|
|
66
|
+
"!src/**/*.spec.ts"
|
|
67
|
+
],
|
|
68
|
+
"output": [
|
|
69
|
+
"lib/**"
|
|
70
|
+
],
|
|
71
|
+
"dependencies": [
|
|
72
|
+
"../clanker-cleanroom:build"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"lint": {
|
|
76
|
+
"command": "vp lint src",
|
|
77
|
+
"files": [
|
|
78
|
+
"src/**/*.ts",
|
|
79
|
+
"tsconfig.json",
|
|
80
|
+
"vite.config.ts"
|
|
81
|
+
],
|
|
82
|
+
"dependencies": [
|
|
83
|
+
"../clanker-cleanroom:build"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"test": {
|
|
87
|
+
"command": "vitest run",
|
|
88
|
+
"files": [
|
|
89
|
+
"src/**/*.ts",
|
|
90
|
+
"vite.config.ts"
|
|
91
|
+
],
|
|
92
|
+
"dependencies": [
|
|
93
|
+
"../clanker-cleanroom:build"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"test:coverage": {
|
|
97
|
+
"command": "vitest run --coverage",
|
|
98
|
+
"files": [
|
|
99
|
+
"src/**/*.ts",
|
|
100
|
+
"vite.config.ts"
|
|
101
|
+
],
|
|
102
|
+
"output": [
|
|
103
|
+
"coverage/**"
|
|
104
|
+
],
|
|
105
|
+
"dependencies": [
|
|
106
|
+
"../clanker-cleanroom:build"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
"prepublishOnly": {
|
|
110
|
+
"dependencies": [
|
|
111
|
+
"lint",
|
|
112
|
+
"test",
|
|
113
|
+
"build"
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"clanker-cleanroom": "0.2.5"
|
|
119
|
+
},
|
|
120
|
+
"devDependencies": {
|
|
121
|
+
"@types/node": "^26.2.0",
|
|
122
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
123
|
+
"typescript": "^7.0.2",
|
|
124
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
|
|
125
|
+
"vite-plus": "0.3.0",
|
|
126
|
+
"vitest": "4.1.11",
|
|
127
|
+
"vitest-gwt": "4.1.4",
|
|
128
|
+
"wireit": "^0.14.13"
|
|
129
|
+
},
|
|
130
|
+
"scripts": {
|
|
131
|
+
"build": "wireit",
|
|
132
|
+
"test": "wireit",
|
|
133
|
+
"lint": "wireit",
|
|
134
|
+
"test:coverage": "wireit"
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/agent.ts
ADDED
package/src/binding.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asRecord,
|
|
3
|
+
parseAgentJsonOutput,
|
|
4
|
+
readTokenCount,
|
|
5
|
+
type AgentBinding,
|
|
6
|
+
type AgentRunResult,
|
|
7
|
+
type DockerVolumeMount,
|
|
8
|
+
} from "clanker-cleanroom";
|
|
9
|
+
|
|
10
|
+
import { CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE } from "./constants.js";
|
|
11
|
+
import { credentialsEnv, resolveClaudeCredentials } from "./credentials.js";
|
|
12
|
+
import { adaptClaudeEvents } from "./trajectory.js";
|
|
13
|
+
|
|
14
|
+
export const claudeBinding: AgentBinding = {
|
|
15
|
+
image: CLAUDE_IMAGE,
|
|
16
|
+
displayName: "Claude",
|
|
17
|
+
trajectoryKind: "claude",
|
|
18
|
+
adaptEvents: adaptClaudeEvents,
|
|
19
|
+
command: ({ prompt, model }) => {
|
|
20
|
+
const claudeArgs = [
|
|
21
|
+
"claude",
|
|
22
|
+
"-p",
|
|
23
|
+
"--output-format",
|
|
24
|
+
"stream-json",
|
|
25
|
+
"--verbose",
|
|
26
|
+
"--dangerously-skip-permissions",
|
|
27
|
+
];
|
|
28
|
+
if (model !== undefined && model !== "") {
|
|
29
|
+
claudeArgs.push("--model", model);
|
|
30
|
+
}
|
|
31
|
+
claudeArgs.push("--", prompt);
|
|
32
|
+
return claudeArgs;
|
|
33
|
+
},
|
|
34
|
+
prepare: async () => {
|
|
35
|
+
const credentials = await resolveClaudeCredentials();
|
|
36
|
+
const volumes: DockerVolumeMount[] = [];
|
|
37
|
+
if (credentials.kind === "credentials-file") {
|
|
38
|
+
volumes.push({
|
|
39
|
+
host: credentials.file,
|
|
40
|
+
container: CLAUDE_CONTAINER_CREDENTIALS_PATH,
|
|
41
|
+
mode: "ro",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
volumes,
|
|
46
|
+
env: credentialsEnv(credentials),
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
parseResult: parseClaudeResult,
|
|
50
|
+
describeFailure: describeClaudeFailure,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function parseClaudeResult(trajectory: string): AgentRunResult {
|
|
54
|
+
const parsed = parseAgentJsonOutput(trajectory);
|
|
55
|
+
if (isErrorResult(parsed)) {
|
|
56
|
+
throw new Error(`Claude agent reported an error: ${describeErrorResult(parsed)}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const record = asRecord(parsed);
|
|
60
|
+
const usage = asRecord(record?.usage);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
durationMs: readTokenCount(record?.duration_ms),
|
|
64
|
+
costUsd: readTokenCount(record?.total_cost_usd),
|
|
65
|
+
usage: {
|
|
66
|
+
inputTokens: readTokenCount(usage?.input_tokens ?? usage?.inputTokens),
|
|
67
|
+
outputTokens: readTokenCount(usage?.output_tokens ?? usage?.outputTokens),
|
|
68
|
+
cacheReadTokens: readTokenCount(usage?.cache_read_input_tokens ?? usage?.cacheReadTokens),
|
|
69
|
+
cacheWriteTokens: readTokenCount(
|
|
70
|
+
usage?.cache_creation_input_tokens ?? usage?.cacheWriteTokens,
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function describeClaudeFailure(trajectory: string): string | undefined {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = parseAgentJsonOutput(trajectory);
|
|
79
|
+
return isErrorResult(parsed) ? describeErrorResult(parsed) : undefined;
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type ClaudeErrorResult = {
|
|
86
|
+
is_error: true;
|
|
87
|
+
result: string;
|
|
88
|
+
subtype?: string;
|
|
89
|
+
terminal_reason?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function isErrorResult(value: unknown): value is ClaudeErrorResult {
|
|
93
|
+
return (
|
|
94
|
+
typeof value === "object" &&
|
|
95
|
+
value !== null &&
|
|
96
|
+
(value as { is_error?: unknown }).is_error === true
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function describeErrorResult(result: ClaudeErrorResult): string {
|
|
101
|
+
const message = String(result.result);
|
|
102
|
+
|
|
103
|
+
if (result.terminal_reason !== undefined && result.terminal_reason !== "completed") {
|
|
104
|
+
return `${result.terminal_reason}: ${message}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (result.subtype !== undefined && result.subtype !== "success") {
|
|
108
|
+
return `${result.subtype}: ${message}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return message;
|
|
112
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CONTAINER_HOME } from "clanker-cleanroom";
|
|
2
|
+
|
|
3
|
+
export const CLAUDE_IMAGE = "clanker-cleanroom/claude";
|
|
4
|
+
export const CLAUDE_CONTAINER_CREDENTIALS_PATH = `${CONTAINER_HOME}/.claude/.credentials.json`;
|
|
5
|
+
|
|
6
|
+
/** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
|
|
7
|
+
export const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
8
|
+
/** Anthropic API key (pay-as-you-go). */
|
|
9
|
+
export const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
|
|
10
|
+
|
|
11
|
+
/** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
|
|
12
|
+
export const defaultClaudeHostCredentialsFile = (home: string): string =>
|
|
13
|
+
`${home}/.claude/.credentials.json`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CLAUDE_API_KEY_ENV,
|
|
7
|
+
CLAUDE_OAUTH_TOKEN_ENV,
|
|
8
|
+
defaultClaudeHostCredentialsFile,
|
|
9
|
+
} from "./constants.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How Claude Code authenticates inside the container. Env-backed kinds are forwarded
|
|
13
|
+
* to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
|
|
14
|
+
* See `constants.ts` for which host setups produce each.
|
|
15
|
+
*/
|
|
16
|
+
export type ClaudeCredentials =
|
|
17
|
+
| { kind: "oauth-token"; token: string }
|
|
18
|
+
| { kind: "api-key"; apiKey: string }
|
|
19
|
+
| { kind: "credentials-file"; file: string };
|
|
20
|
+
|
|
21
|
+
/** Env OAuth token, then env API key, then a readable host credentials file. */
|
|
22
|
+
export async function resolveClaudeCredentials(
|
|
23
|
+
options: { env?: NodeJS.ProcessEnv; home?: string } = {},
|
|
24
|
+
): Promise<ClaudeCredentials> {
|
|
25
|
+
const env = options.env ?? process.env;
|
|
26
|
+
const home = options.home ?? homedir();
|
|
27
|
+
|
|
28
|
+
const token = env[CLAUDE_OAUTH_TOKEN_ENV];
|
|
29
|
+
if (token !== undefined && token !== "") {
|
|
30
|
+
return { kind: "oauth-token", token };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const apiKey = env[CLAUDE_API_KEY_ENV];
|
|
34
|
+
if (apiKey !== undefined && apiKey !== "") {
|
|
35
|
+
return { kind: "api-key", apiKey };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const file = defaultClaudeHostCredentialsFile(home);
|
|
39
|
+
try {
|
|
40
|
+
await access(file, fsConstants.R_OK);
|
|
41
|
+
} catch {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Claude Code credentials not found. Set ${CLAUDE_OAUTH_TOKEN_ENV} (run \`claude setup-token\` on the host) ` +
|
|
44
|
+
`or ${CLAUDE_API_KEY_ENV}, or provide ${file} (Linux hosts; macOS keeps credentials in the Keychain).`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { kind: "credentials-file", file };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
|
|
52
|
+
export function credentialsEnv(credentials: ClaudeCredentials): Record<string, string> {
|
|
53
|
+
switch (credentials.kind) {
|
|
54
|
+
case "oauth-token":
|
|
55
|
+
return { [CLAUDE_OAUTH_TOKEN_ENV]: credentials.token };
|
|
56
|
+
case "api-key":
|
|
57
|
+
return { [CLAUDE_API_KEY_ENV]: credentials.apiKey };
|
|
58
|
+
case "credentials-file":
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CLAUDE_API_KEY_ENV,
|
|
3
|
+
CLAUDE_CONTAINER_CREDENTIALS_PATH,
|
|
4
|
+
CLAUDE_IMAGE,
|
|
5
|
+
CLAUDE_OAUTH_TOKEN_ENV,
|
|
6
|
+
defaultClaudeHostCredentialsFile,
|
|
7
|
+
} from "./constants.js";
|
|
8
|
+
export { claudeBinding } from "./binding.js";
|
|
9
|
+
export { resolveClaudeCredentials, credentialsEnv, type ClaudeCredentials } from "./credentials.js";
|
|
10
|
+
export { claudeAgent } from "./agent.js";
|
|
11
|
+
export { adaptClaudeEvents } from "./trajectory.js";
|
|
12
|
+
export { DOCKER_DIR, PACKAGE_ROOT } from "./package-root.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
/** Absolute path to this package root (parent of `src/` or `lib/`). */
|
|
5
|
+
export const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
6
|
+
|
|
7
|
+
/** Folder of `*.Dockerfile` files for this agent. */
|
|
8
|
+
export const DOCKER_DIR = join(PACKAGE_ROOT, "docker");
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asRecord,
|
|
3
|
+
extractTextContent,
|
|
4
|
+
readString,
|
|
5
|
+
readTokenCount,
|
|
6
|
+
type TrajectoryEvent,
|
|
7
|
+
} from "clanker-cleanroom";
|
|
8
|
+
|
|
9
|
+
export function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[] {
|
|
10
|
+
const events: TrajectoryEvent[] = [];
|
|
11
|
+
|
|
12
|
+
for (const raw of rawEvents) {
|
|
13
|
+
const record = asRecord(raw);
|
|
14
|
+
if (record === undefined) {
|
|
15
|
+
events.push({ kind: "other", type: "unknown", raw });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const type = readString(record.type) ?? "unknown";
|
|
20
|
+
|
|
21
|
+
switch (type) {
|
|
22
|
+
case "system":
|
|
23
|
+
events.push({
|
|
24
|
+
kind: "system",
|
|
25
|
+
sessionId: readString(record.session_id),
|
|
26
|
+
model: readString(record.model),
|
|
27
|
+
raw,
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
case "user":
|
|
31
|
+
events.push(...adaptClaudeUser(record, raw));
|
|
32
|
+
break;
|
|
33
|
+
case "assistant":
|
|
34
|
+
events.push(...adaptClaudeAssistant(record, raw));
|
|
35
|
+
break;
|
|
36
|
+
case "result":
|
|
37
|
+
events.push({
|
|
38
|
+
kind: "result",
|
|
39
|
+
text: readString(record.result) ?? "",
|
|
40
|
+
isError: record.is_error === true,
|
|
41
|
+
durationMs: readTokenCount(record.duration_ms),
|
|
42
|
+
costUsd: readTokenCount(record.total_cost_usd),
|
|
43
|
+
raw,
|
|
44
|
+
});
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
events.push({ kind: "other", type, raw });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return events;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function adaptClaudeAssistant(
|
|
55
|
+
record: Record<string, unknown>,
|
|
56
|
+
raw: unknown,
|
|
57
|
+
): TrajectoryEvent[] {
|
|
58
|
+
const events: TrajectoryEvent[] = [];
|
|
59
|
+
const text = extractTextContent(record.message);
|
|
60
|
+
if (text !== "") {
|
|
61
|
+
events.push({ kind: "assistant", text, raw });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const block of contentBlocks(record.message)) {
|
|
65
|
+
if (block.type !== "tool_use") {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
events.push({
|
|
69
|
+
kind: "tool",
|
|
70
|
+
phase: "started",
|
|
71
|
+
callId: readString(block.id) ?? "",
|
|
72
|
+
name: readString(block.name) ?? "unknown",
|
|
73
|
+
args: block.input ?? null,
|
|
74
|
+
result: null,
|
|
75
|
+
raw,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (events.length === 0) {
|
|
80
|
+
events.push({ kind: "assistant", text: "", raw });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return events;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function adaptClaudeUser(record: Record<string, unknown>, raw: unknown): TrajectoryEvent[] {
|
|
87
|
+
const events: TrajectoryEvent[] = [];
|
|
88
|
+
const text = extractTextContent(record.message);
|
|
89
|
+
const toolResults: TrajectoryEvent[] = [];
|
|
90
|
+
|
|
91
|
+
for (const block of contentBlocks(record.message)) {
|
|
92
|
+
if (block.type !== "tool_result") {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
toolResults.push({
|
|
96
|
+
kind: "tool",
|
|
97
|
+
phase: "completed",
|
|
98
|
+
callId: readString(block.tool_use_id) ?? "",
|
|
99
|
+
name: "tool_result",
|
|
100
|
+
args: null,
|
|
101
|
+
result: block.content ?? null,
|
|
102
|
+
raw,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (text !== "" || toolResults.length === 0) {
|
|
107
|
+
events.push({ kind: "user", text, raw });
|
|
108
|
+
}
|
|
109
|
+
events.push(...toolResults);
|
|
110
|
+
return events;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function contentBlocks(message: unknown): Record<string, unknown>[] {
|
|
114
|
+
const content = asRecord(message)?.content;
|
|
115
|
+
if (!Array.isArray(content)) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
return content.map((block) => asRecord(block)).filter((block) => block !== undefined);
|
|
119
|
+
}
|