subharness 0.0.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/LICENSE +21 -0
- package/README.md +158 -0
- package/dist/adapters/claude-auth.d.ts +7 -0
- package/dist/adapters/claude-auth.js +87 -0
- package/dist/adapters/claude-auth.js.map +1 -0
- package/dist/adapters/claude-config.d.ts +6 -0
- package/dist/adapters/claude-config.js +37 -0
- package/dist/adapters/claude-config.js.map +1 -0
- package/dist/adapters/claude-input.d.ts +9 -0
- package/dist/adapters/claude-input.js +32 -0
- package/dist/adapters/claude-input.js.map +1 -0
- package/dist/adapters/claude-permissions.d.ts +3 -0
- package/dist/adapters/claude-permissions.js +24 -0
- package/dist/adapters/claude-permissions.js.map +1 -0
- package/dist/adapters/claude-settings.d.ts +14 -0
- package/dist/adapters/claude-settings.js +43 -0
- package/dist/adapters/claude-settings.js.map +1 -0
- package/dist/adapters/claude-tools.d.ts +9 -0
- package/dist/adapters/claude-tools.js +47 -0
- package/dist/adapters/claude-tools.js.map +1 -0
- package/dist/adapters/claude.d.ts +22 -0
- package/dist/adapters/claude.js +295 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex-config.d.ts +25 -0
- package/dist/adapters/codex-config.js +66 -0
- package/dist/adapters/codex-config.js.map +1 -0
- package/dist/adapters/codex.d.ts +2 -0
- package/dist/adapters/codex.js +238 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/fx-auth.d.ts +4 -0
- package/dist/adapters/fx-auth.js +116 -0
- package/dist/adapters/fx-auth.js.map +1 -0
- package/dist/adapters/fx-rpc.d.ts +29 -0
- package/dist/adapters/fx-rpc.js +124 -0
- package/dist/adapters/fx-rpc.js.map +1 -0
- package/dist/adapters/fx-tools.d.ts +19 -0
- package/dist/adapters/fx-tools.js +119 -0
- package/dist/adapters/fx-tools.js.map +1 -0
- package/dist/adapters/fx.d.ts +3 -0
- package/dist/adapters/fx.js +302 -0
- package/dist/adapters/fx.js.map +1 -0
- package/dist/adapters/mcp-tool-content.d.ts +12 -0
- package/dist/adapters/mcp-tool-content.js +8 -0
- package/dist/adapters/mcp-tool-content.js.map +1 -0
- package/dist/adapters/rpc.d.ts +28 -0
- package/dist/adapters/rpc.js +101 -0
- package/dist/adapters/rpc.js.map +1 -0
- package/dist/adapters/types.d.ts +28 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli/args.d.ts +18 -0
- package/dist/cli/args.js +118 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/catalog-worker.d.ts +1 -0
- package/dist/cli/catalog-worker.js +22 -0
- package/dist/cli/catalog-worker.js.map +1 -0
- package/dist/cli/catalog.d.ts +26 -0
- package/dist/cli/catalog.js +41 -0
- package/dist/cli/catalog.js.map +1 -0
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.js +68 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/input.d.ts +5 -0
- package/dist/cli/input.js +69 -0
- package/dist/cli/input.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +105 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +7 -0
- package/dist/cli/output.js +43 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/access.d.ts +21 -0
- package/dist/config/access.js +97 -0
- package/dist/config/access.js.map +1 -0
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.js +80 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/oidc.d.ts +1 -0
- package/dist/config/oidc.js +53 -0
- package/dist/config/oidc.js.map +1 -0
- package/dist/config/project.d.ts +5 -0
- package/dist/config/project.js +56 -0
- package/dist/config/project.js.map +1 -0
- package/dist/config/resolve-access.d.ts +4 -0
- package/dist/config/resolve-access.js +42 -0
- package/dist/config/resolve-access.js.map +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/client.d.ts +9 -0
- package/dist/runtime/client.js +91 -0
- package/dist/runtime/client.js.map +1 -0
- package/dist/runtime/coordinator.d.ts +61 -0
- package/dist/runtime/coordinator.js +296 -0
- package/dist/runtime/coordinator.js.map +1 -0
- package/dist/runtime/daemon.d.ts +1 -0
- package/dist/runtime/daemon.js +28 -0
- package/dist/runtime/daemon.js.map +1 -0
- package/dist/runtime/definition.d.ts +6 -0
- package/dist/runtime/definition.js +19 -0
- package/dist/runtime/definition.js.map +1 -0
- package/dist/runtime/native-owner.d.ts +8 -0
- package/dist/runtime/native-owner.js +25 -0
- package/dist/runtime/native-owner.js.map +1 -0
- package/dist/runtime/observation.d.ts +1 -0
- package/dist/runtime/observation.js +15 -0
- package/dist/runtime/observation.js.map +1 -0
- package/dist/runtime/select-native.d.ts +3 -0
- package/dist/runtime/select-native.js +34 -0
- package/dist/runtime/select-native.js.map +1 -0
- package/dist/runtime/service.d.ts +5 -0
- package/dist/runtime/service.js +90 -0
- package/dist/runtime/service.js.map +1 -0
- package/dist/runtime/session-launcher.d.ts +6 -0
- package/dist/runtime/session-launcher.js +29 -0
- package/dist/runtime/session-launcher.js.map +1 -0
- package/dist/runtime/state.d.ts +14 -0
- package/dist/runtime/state.js +69 -0
- package/dist/runtime/state.js.map +1 -0
- package/dist/runtime/types.d.ts +81 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/worker-client.d.ts +3 -0
- package/dist/runtime/worker-client.js +81 -0
- package/dist/runtime/worker-client.js.map +1 -0
- package/dist/runtime/worker.d.ts +1 -0
- package/dist/runtime/worker.js +67 -0
- package/dist/runtime/worker.js.map +1 -0
- package/dist/sdk/definitions.d.ts +6 -0
- package/dist/sdk/definitions.js +72 -0
- package/dist/sdk/definitions.js.map +1 -0
- package/dist/sdk/tool-result.d.ts +23 -0
- package/dist/sdk/tool-result.js +95 -0
- package/dist/sdk/tool-result.js.map +1 -0
- package/dist/sdk/tools.d.ts +9 -0
- package/dist/sdk/tools.js +73 -0
- package/dist/sdk/tools.js.map +1 -0
- package/dist/sdk/types.d.ts +49 -0
- package/dist/sdk/types.js +2 -0
- package/dist/sdk/types.js.map +1 -0
- package/dist/sdk/validation.d.ts +5 -0
- package/dist/sdk/validation.js +20 -0
- package/dist/sdk/validation.js.map +1 -0
- package/dist/shell.d.ts +2 -0
- package/dist/shell.js +5 -0
- package/dist/shell.js.map +1 -0
- package/package.json +61 -0
- package/sdk/access-config.md +52 -0
- package/sdk/adapter-contract.md +33 -0
- package/sdk/agent.md +52 -0
- package/sdk/authentication.md +11 -0
- package/sdk/cli/index.md +67 -0
- package/sdk/cli/output.md +28 -0
- package/sdk/completion-notifications.md +29 -0
- package/sdk/config.md +41 -0
- package/sdk/distribution.md +31 -0
- package/sdk/fx.md +71 -0
- package/sdk/harnesses.md +32 -0
- package/sdk/index.md +22 -0
- package/sdk/message-delivery.md +39 -0
- package/sdk/plugins/sub-agents.md +57 -0
- package/sdk/project-team.md +57 -0
- package/sdk/sessions.md +31 -0
- package/sdk/tools.md +73 -0
- package/sdk/v1-runtime.md +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agent Draft 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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Subharness
|
|
2
|
+
|
|
3
|
+
Run Codex, Claude Code, or fx from your everyday coding assistant through the `subharness` CLI. Add reusable specialists in TypeScript when a task needs shared instructions, tools, or harness alternatives. V1 connects installed Codex, Claude Code, and fx harnesses. Codex and Claude Code reuse eligible native subscription logins or explicit API access; fx connects through explicit Vercel AI Gateway access.
|
|
4
|
+
|
|
5
|
+
The library coordinates sessions, queues, and subagents. Harnesses own model execution, native tools, and context. You supply the working directory and any sandbox or worktree isolation.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Node.js 22.18 or newer is required. Install the CLI from npm:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install --global subharness
|
|
13
|
+
|
|
14
|
+
cd /path/to/your-project
|
|
15
|
+
subharness --help
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Global installation exposes `subharness` on PATH, with `agent` retained as a compatibility alias. You can also use `npx subharness` without installing a global executable. Direct harness runs need no TypeScript file or SDK dependency in the target project. Install and authenticate the native harness separately. Specialist definitions need a project-local SDK dependency so imports resolve; global definitions likewise need a reachable SDK dependency.
|
|
19
|
+
|
|
20
|
+
See [package distribution](sdk/distribution.md) for development checkouts, local tarballs, and package boundaries. Subharness uses the [MIT license](LICENSE).
|
|
21
|
+
|
|
22
|
+
## Run a harness directly
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
subharness run claude "Review the current diff."
|
|
26
|
+
subharness run codex --cwd /path/to/worktree "Implement the documented feature."
|
|
27
|
+
subharness run fx --model "provider/model" "Compare the proposed implementations."
|
|
28
|
+
subharness run claude --help
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Replace `provider/model` with an available Gateway model identifier. fx requires an explicit Gateway connection as described under Access below. Without `--model`, a direct target uses a verifiable native default for the authorized connection and retains that model for follow-ups. If the default cannot be resolved, the command asks for an explicit model. `--effort` uses the selected harness's supported values. These options do not override a TypeScript specialist.
|
|
32
|
+
|
|
33
|
+
Prompts can be one quoted argument, `--prompt`, or a UTF-8 file. `--prompt-file -` reads piped stdin explicitly. Exactly one prompt source is accepted, with a 1 MiB limit.
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
subharness run claude --prompt-file ./review-task.txt
|
|
37
|
+
subharness run codex --prompt-file - < ./task.txt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Define a reusable specialist
|
|
41
|
+
|
|
42
|
+
Install the SDK in the project before adding definitions:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npm install --save-dev subharness
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Create `.agents/agents/developer.agent.ts` in your repository:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { agent, codex, claudeCode } from "subharness";
|
|
52
|
+
|
|
53
|
+
export default agent({
|
|
54
|
+
name: "developer",
|
|
55
|
+
description: "Implements features and verifies changes.",
|
|
56
|
+
instructions: "Follow the repository's documented requirements and run its checks.",
|
|
57
|
+
harness: [
|
|
58
|
+
codex({ model: "CODEX_MODEL_ID", effort: "high" }),
|
|
59
|
+
claudeCode({ model: "CLAUDE_MODEL_ID", effort: "high" }),
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Replace the model placeholders with identifiers available through your native harnesses. Each file default-exports one definition. Global definitions live in `~/.agents/agents/`; repository and global names never silently override one another. The bare targets `codex`, `claude`, and `fx` are reserved for harnesses; a specialist with one of those names requires a scope qualifier such as `repo:claude`.
|
|
65
|
+
|
|
66
|
+
## Run and follow up
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
subharness list
|
|
70
|
+
subharness run repo:developer --cwd /path/to/worktree --prompt "Implement the documented feature."
|
|
71
|
+
subharness send <session-id> --delivery queue --prompt "Add the integration tests."
|
|
72
|
+
subharness send <session-id> --delivery steer --prompt "Keep the existing API compatible."
|
|
73
|
+
subharness wait <task-id> --after <response-id>
|
|
74
|
+
subharness status <task-id> --full
|
|
75
|
+
subharness cancel <task-id>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Commands print task/session identifiers and return one complete response with its response identifier and task state. Pending descendant work can continue after that command exits. Run or wait commands can use your assistant's background-task facilities. Native steering is used when supported; otherwise `steer` behaves as `interrupt`, cancels affected descendants, preserves independently queued tasks, and reports the effective mode.
|
|
79
|
+
|
|
80
|
+
Long prompts can use `--prompt-file`. Output is compact text by default; `--format jsonl` selects typed integration records without forwarding native token or tool streams.
|
|
81
|
+
|
|
82
|
+
## Access
|
|
83
|
+
|
|
84
|
+
No project configuration is needed when an eligible native subscription login is available. Login remains with the native harness. Ambient API keys do not silently enable paid fallback.
|
|
85
|
+
|
|
86
|
+
Personal access preferences can be placed in the ignored `.agents/agents.local.json`. Linked worktrees read that file from the main checkout, while their agent definitions come from the selected worktree.
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"access": {
|
|
91
|
+
"codex": [{ "type": "subscription" }],
|
|
92
|
+
"claudeCode": [{ "type": "vercel-api-key", "env": "AI_GATEWAY_API_KEY" }]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Only credential references belong in that file. [Access configuration](sdk/access-config.md) also describes direct API keys, project OIDC, explicit connection fallback, and token-lifetime limits.
|
|
98
|
+
|
|
99
|
+
For [fx](sdk/fx.md), select a Gateway connection explicitly:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"access": {
|
|
104
|
+
"fx": [{ "type": "vercel-api-key", "env": "API_KEY", "envFile": ".env" }]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use `fx({ model: "google/gemini-3.8-flash" })` in the agent's `harness` field. Native fx must select the supplied environment credential instead of a saved login. Gateway model and provider restrictions remain effective; catalog availability alone does not establish access for a particular team.
|
|
110
|
+
|
|
111
|
+
## Tools and delegation
|
|
112
|
+
|
|
113
|
+
Agents can expose validated [custom tools](sdk/tools.md) and declare [subagents](sdk/plugins/sub-agents.md). A managed parent invokes its child with the same CLI:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
subharness run subagent:reviewer --cwd /path/to/worktree --prompt "Review the implementation."
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The library supplies the parent context. Declaring `subagents` authorizes their invocation. Claude Code receives permissions scoped to its session launcher, declared children, and coordination commands; native approval rules and sandbox restrictions still apply. Children receive explicit task context rather than a copy of the parent's transcript. Cancellation propagates through delegated descendants; a parent task completes after child results and its own continuation are handled.
|
|
120
|
+
|
|
121
|
+
This checkout includes five [repository roles](sdk/project-team.md): architecture and design with Astra, implementation with Sol, independent review with Opus, research with Gemini, and visual and shader work with Fable 5.1 through fx. Reusable techniques live in `.agents/skills/` and are read only when the task needs them; a new technique does not require another agent definition. Discover the roles with `subharness list` and invoke them through `repo:<name>`. Skill loading, native capability limits, and the image-task execution path are documented with the team.
|
|
122
|
+
|
|
123
|
+
## Boundaries
|
|
124
|
+
|
|
125
|
+
The local coordinator retains sessions and responses across command exits, but does not restore queues after coordinator or environment loss. External assistant reactivation depends on that assistant's host. Native requests needing interactive permission/input fail with actionable `INPUT_REQUIRED`; they are not automatically approved. CLI delegation requires the native shell to reach the coordinator over loopback HTTP; a sandbox that blocks local networking must be configured by the caller.
|
|
126
|
+
|
|
127
|
+
`resume` is available as a command, but v1 adapters report `RECOVERY_UNSUPPORTED` when prompt-free native recovery is unavailable. Failed work is not automatically replayed or migrated.
|
|
128
|
+
|
|
129
|
+
Live subscription checks with Codex and Claude Code cover execution, conversation follow-ups, custom tools, and queuing. Codex native steering and interruption preserve queued work. Claude's `steer` fallback also interrupts the active task, runs its replacement, and preserves queued work.
|
|
130
|
+
|
|
131
|
+
Live Claude checks also cover launching a declared child and sending follow-ups to that same child session. Explicit native approval rules remain effective. Codex nested delegation remains blocked by local network restrictions in the tested sandbox.
|
|
132
|
+
|
|
133
|
+
Live fx checks through an explicit Gateway API key cover execution, conversation follow-ups, and custom tools with Gemini 3.8 Flash and Kimi K3. Gemini checks also cover interruption, pending tool completion, and preservation of queued tasks. Native fx permissions remain unchanged. OIDC billing attribution has not been validated end to end. Automated tests use external-protocol fixtures and do not require model access.
|
|
134
|
+
|
|
135
|
+
## Checks and contracts
|
|
136
|
+
|
|
137
|
+
The private `apps/docs` workspace contains the Geistdocs website. Its reference pages are generated from the authoritative `sdk/` Markdown files; edit those originals rather than generated MDX.
|
|
138
|
+
|
|
139
|
+
Website development requires a full `npm install` from the repository root, including workspace dependencies.
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
npm run dev:site
|
|
143
|
+
npm run check:site
|
|
144
|
+
npm run build:site
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The [website contracts](website/architecture.md) describe the application boundaries, and the [mascot asset documentation](assets/mascot/README.md) records the reproducible geometry conversion and poster rendering commands.
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm run check
|
|
151
|
+
npm run check:package
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`npm run check` builds the current SDK first, then runs TypeScript checking and behavioral tests. Building first lets repository agent definitions import the package's current exports on a clean checkout and prevents tests from exercising stale build output. Tests cover native protocol boundaries, credential selection, worktrees, task queues, cancellation races, nested CLI execution, and process lifetime.
|
|
155
|
+
|
|
156
|
+
`npm run check:package` packs the SDK and verifies installation, imports, and CLI agent discovery in a temporary consumer project. It downloads dependencies from the public npm registry with install lifecycle scripts disabled and does not run a coding model.
|
|
157
|
+
|
|
158
|
+
The [SDK documentation](sdk/index.md) describes the final contracts. [AGENTS.md](AGENTS.md) defines the project's documentation, TDD, and review workflow. Comparative research and review evidence are kept in the ignored `.context` directory.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AccountInfo, Options, Settings, ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { AdapterOptions, ResolvedAccess } from "./types.js";
|
|
3
|
+
export declare function verifyClaudeAccess(account: AccountInfo, access: ResolvedAccess): void;
|
|
4
|
+
export declare function claudeSettings(options: AdapterOptions): Pick<Options, "env" | "settings" | "effort">;
|
|
5
|
+
/** Managed policy has higher precedence than flag settings; fail instead of changing billing. */
|
|
6
|
+
export declare function verifyClaudePolicy(policies: Settings[], selected: Settings): void;
|
|
7
|
+
export declare function verifyClaudeOptions(models: ModelInfo[], options: AdapterOptions, selectedModel?: string | undefined): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
export function verifyClaudeAccess(account, access) {
|
|
3
|
+
if (account.apiProvider !== "firstParty") {
|
|
4
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code selected a different provider. Check its native authentication settings.");
|
|
5
|
+
}
|
|
6
|
+
if (access.type === "subscription") {
|
|
7
|
+
const subscription = ["Claude Pro", "Claude Max", "Claude Team", "Claude Enterprise"].includes(account.subscriptionType ?? "");
|
|
8
|
+
if (!subscription || (account.apiKeySource !== undefined && account.apiKeySource !== "none") || (account.tokenSource !== undefined && account.tokenSource !== "claude.ai")) {
|
|
9
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code subscription access could not be verified. Sign in with your Claude account using claude auth login.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else if (access.type === "api-key") {
|
|
13
|
+
if (account.apiKeySource !== "ANTHROPIC_API_KEY") {
|
|
14
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code did not select the configured API credential.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else if ((account.apiKeySource !== undefined && account.apiKeySource !== "none") || account.subscriptionType) {
|
|
18
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code did not select the configured Gateway credential.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const routeVariables = [
|
|
22
|
+
"ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "ANTHROPIC_CUSTOM_HEADERS",
|
|
23
|
+
"ANTHROPIC_PROFILE", "ANTHROPIC_AWS_API_KEY", "ANTHROPIC_UNIX_SOCKET", "CLAUDE_CODE_OAUTH_TOKEN",
|
|
24
|
+
"CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR", "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR",
|
|
25
|
+
"CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST",
|
|
26
|
+
"CLAUDE_CODE_NO_MODEL_FALLBACK",
|
|
27
|
+
"CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_USE_FOUNDRY",
|
|
28
|
+
"CLAUDE_CODE_USE_ANTHROPIC_AWS", "CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD", "CLAUDE_CODE_USE_MANTLE",
|
|
29
|
+
];
|
|
30
|
+
export function claudeSettings(options) {
|
|
31
|
+
if (options.harness.fast && options.access.type === "subscription") {
|
|
32
|
+
throw new AgentError("UNSUPPORTED_OPTION", "Claude fast mode requires an explicitly selected paid API or Gateway connection.");
|
|
33
|
+
}
|
|
34
|
+
const selected = Object.fromEntries(routeVariables.map(key => [key, ""]));
|
|
35
|
+
if (options.access.type !== "subscription") {
|
|
36
|
+
if (!options.access.token)
|
|
37
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "The selected Claude Code credential is unavailable.");
|
|
38
|
+
if (options.access.type === "api-key") {
|
|
39
|
+
selected.ANTHROPIC_API_KEY = options.access.token;
|
|
40
|
+
selected.ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
selected.ANTHROPIC_AUTH_TOKEN = options.access.token;
|
|
44
|
+
selected.ANTHROPIC_BASE_URL = "https://ai-gateway.vercel.sh/claude-code";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
selected.CLAUDE_CODE_DISABLE_FAST_MODE = options.harness.fast ? "" : "1";
|
|
48
|
+
selected.CLAUDE_CODE_NO_MODEL_FALLBACK = "1";
|
|
49
|
+
if (options.harness.effort)
|
|
50
|
+
selected.CLAUDE_CODE_EFFORT_LEVEL = options.harness.effort;
|
|
51
|
+
return {
|
|
52
|
+
env: { ...options.env, ...selected },
|
|
53
|
+
effort: options.harness.effort,
|
|
54
|
+
settings: { env: selected, apiKeyHelper: "", fastMode: options.harness.fast, fallbackModel: [] },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Managed policy has higher precedence than flag settings; fail instead of changing billing. */
|
|
58
|
+
export function verifyClaudePolicy(policies, selected) {
|
|
59
|
+
for (const policy of policies) {
|
|
60
|
+
if (policy.apiKeyHelper || policy.policyHelper) {
|
|
61
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code managed credential helpers prevent verifying the selected access method.");
|
|
62
|
+
}
|
|
63
|
+
for (const [name, expected] of Object.entries(selected.env ?? {})) {
|
|
64
|
+
const enforced = policy.env?.[name];
|
|
65
|
+
if (enforced !== undefined && enforced !== expected) {
|
|
66
|
+
throw new AgentError("ACCESS_UNAVAILABLE", "Claude Code managed settings conflict with the selected access or execution options.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if ((policy.fastMode !== undefined && policy.fastMode !== selected.fastMode) || policy.fallbackModel?.length) {
|
|
70
|
+
throw new AgentError("UNSUPPORTED_OPTION", "Claude Code managed settings prevent applying the requested execution options.");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function verifyClaudeOptions(models, options, selectedModel = options.harness.model) {
|
|
75
|
+
const requested = options.harness;
|
|
76
|
+
const model = models.find(model => model.value === selectedModel || model.resolvedModel === selectedModel);
|
|
77
|
+
if (!model)
|
|
78
|
+
return; // Custom endpoints can expose IDs absent from the native catalog.
|
|
79
|
+
if (requested.fast && model.supportsFastMode === false) {
|
|
80
|
+
throw new AgentError("UNSUPPORTED_OPTION", "The selected Claude model does not support fast mode.");
|
|
81
|
+
}
|
|
82
|
+
if (requested.effort && (model.supportsEffort === false ||
|
|
83
|
+
(model.supportedEffortLevels && !model.supportedEffortLevels.includes(requested.effort)))) {
|
|
84
|
+
throw new AgentError("UNSUPPORTED_OPTION", "The selected Claude model does not support the requested effort.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=claude-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-auth.js","sourceRoot":"","sources":["../../src/adapters/claude-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AccountInfo, Options, EffortLevel, Settings, ModelInfo } from \"@anthropic-ai/claude-agent-sdk\";\nimport { AgentError } from \"../errors.js\";\nimport type { AdapterOptions, ResolvedAccess } from \"./types.js\";\n\nexport function verifyClaudeAccess(account: AccountInfo, access: ResolvedAccess): void {\n if (account.apiProvider !== \"firstParty\") {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code selected a different provider. Check its native authentication settings.\");\n }\n if (access.type === \"subscription\") {\n const subscription = [\"Claude Pro\", \"Claude Max\", \"Claude Team\", \"Claude Enterprise\"].includes(account.subscriptionType ?? \"\");\n if (!subscription || (account.apiKeySource !== undefined && account.apiKeySource !== \"none\") || (account.tokenSource !== undefined && account.tokenSource !== \"claude.ai\")) {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code subscription access could not be verified. Sign in with your Claude account using claude auth login.\");\n }\n } else if (access.type === \"api-key\") {\n if (account.apiKeySource !== \"ANTHROPIC_API_KEY\") {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code did not select the configured API credential.\");\n }\n } else if ((account.apiKeySource !== undefined && account.apiKeySource !== \"none\") || account.subscriptionType) {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code did not select the configured Gateway credential.\");\n }\n}\n\nconst routeVariables = [\n \"ANTHROPIC_API_KEY\", \"ANTHROPIC_AUTH_TOKEN\", \"ANTHROPIC_BASE_URL\", \"ANTHROPIC_CUSTOM_HEADERS\",\n \"ANTHROPIC_PROFILE\", \"ANTHROPIC_AWS_API_KEY\", \"ANTHROPIC_UNIX_SOCKET\", \"CLAUDE_CODE_OAUTH_TOKEN\",\n \"CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR\", \"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR\",\n \"CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST\",\n \"CLAUDE_CODE_NO_MODEL_FALLBACK\",\n \"CLAUDE_CODE_USE_BEDROCK\", \"CLAUDE_CODE_USE_VERTEX\", \"CLAUDE_CODE_USE_FOUNDRY\",\n \"CLAUDE_CODE_USE_ANTHROPIC_AWS\", \"CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD\", \"CLAUDE_CODE_USE_MANTLE\",\n];\n\nexport function claudeSettings(options: AdapterOptions): Pick<Options, \"env\" | \"settings\" | \"effort\"> {\n if (options.harness.fast && options.access.type === \"subscription\") {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"Claude fast mode requires an explicitly selected paid API or Gateway connection.\");\n }\n const selected: Record<string, string> = Object.fromEntries(routeVariables.map(key => [key, \"\"]));\n if (options.access.type !== \"subscription\") {\n if (!options.access.token) throw new AgentError(\"ACCESS_UNAVAILABLE\", \"The selected Claude Code credential is unavailable.\");\n if (options.access.type === \"api-key\") {\n selected.ANTHROPIC_API_KEY = options.access.token;\n selected.ANTHROPIC_BASE_URL = \"https://api.anthropic.com\";\n } else {\n selected.ANTHROPIC_AUTH_TOKEN = options.access.token;\n selected.ANTHROPIC_BASE_URL = \"https://ai-gateway.vercel.sh/claude-code\";\n }\n }\n selected.CLAUDE_CODE_DISABLE_FAST_MODE = options.harness.fast ? \"\" : \"1\";\n selected.CLAUDE_CODE_NO_MODEL_FALLBACK = \"1\";\n if (options.harness.effort) selected.CLAUDE_CODE_EFFORT_LEVEL = options.harness.effort;\n return {\n env: {...options.env, ...selected},\n effort: options.harness.effort as EffortLevel | undefined,\n settings: {env: selected, apiKeyHelper: \"\", fastMode: options.harness.fast, fallbackModel: []},\n };\n}\n\n\n/** Managed policy has higher precedence than flag settings; fail instead of changing billing. */\nexport function verifyClaudePolicy(policies: Settings[], selected: Settings): void {\n for (const policy of policies) {\n if (policy.apiKeyHelper || policy.policyHelper) {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code managed credential helpers prevent verifying the selected access method.\");\n }\n for (const [name, expected] of Object.entries(selected.env ?? {})) {\n const enforced = policy.env?.[name];\n if (enforced !== undefined && enforced !== expected) {\n throw new AgentError(\"ACCESS_UNAVAILABLE\", \"Claude Code managed settings conflict with the selected access or execution options.\");\n }\n }\n if ((policy.fastMode !== undefined && policy.fastMode !== selected.fastMode) || policy.fallbackModel?.length) {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"Claude Code managed settings prevent applying the requested execution options.\");\n }\n }\n}\n\n\nexport function verifyClaudeOptions(models: ModelInfo[], options: AdapterOptions, selectedModel = options.harness.model): void {\n const requested = options.harness;\n const model = models.find(model => model.value === selectedModel || model.resolvedModel === selectedModel);\n if (!model) return; // Custom endpoints can expose IDs absent from the native catalog.\n if (requested.fast && model.supportsFastMode === false) {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"The selected Claude model does not support fast mode.\");\n }\n if (requested.effort && (model.supportsEffort === false ||\n (model.supportedEffortLevels && !model.supportedEffortLevels.includes(requested.effort as EffortLevel)))) {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"The selected Claude model does not support the requested effort.\");\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Settings } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
/** Pass only a pathname in native arguments, never an inline credential-bearing settings object. */
|
|
3
|
+
export declare function privateClaudeConfig(settings: Settings): Promise<{
|
|
4
|
+
path: string;
|
|
5
|
+
cleanup: () => Promise<void>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
const directories = new Set();
|
|
6
|
+
let cleanupRegistered = false;
|
|
7
|
+
/** Pass only a pathname in native arguments, never an inline credential-bearing settings object. */
|
|
8
|
+
export async function privateClaudeConfig(settings) {
|
|
9
|
+
const directory = await mkdtemp(join(tmpdir(), "subharness-claude-"));
|
|
10
|
+
directories.add(directory);
|
|
11
|
+
if (!cleanupRegistered) {
|
|
12
|
+
cleanupRegistered = true;
|
|
13
|
+
process.once("exit", () => {
|
|
14
|
+
for (const owned of directories) {
|
|
15
|
+
try {
|
|
16
|
+
rmSync(owned, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
catch { /* Best effort on process exit. */ }
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const cleanup = async () => {
|
|
23
|
+
await rm(directory, { recursive: true, force: true });
|
|
24
|
+
directories.delete(directory);
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
await chmod(directory, 0o700);
|
|
28
|
+
const path = join(directory, "settings.json");
|
|
29
|
+
await writeFile(path, JSON.stringify(settings), { mode: 0o600, flag: "wx" });
|
|
30
|
+
return { path, cleanup };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
await cleanup();
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=claude-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-config.js","sourceRoot":"","sources":["../../src/adapters/claude-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;AACtC,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B,oGAAoG;AACpG,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAkB;IAC1D,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACtE,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,iBAAiB,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;YACxB,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;gBAChC,IAAI,CAAC;oBAAC,MAAM,CAAC,KAAK,EAAE,EAAC,SAAS,EAAC,IAAI,EAAE,KAAK,EAAC,IAAI,EAAC,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,kCAAkC,CAAC,CAAC;YACnG,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,MAAM,EAAE,CAAC,SAAS,EAAE,EAAC,SAAS,EAAC,IAAI,EAAE,KAAK,EAAC,IAAI,EAAC,CAAC,CAAC;QAClD,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC,CAAC;IACF,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAC9C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAC,IAAI,EAAC,KAAK,EAAE,IAAI,EAAC,IAAI,EAAC,CAAC,CAAC;QACzE,OAAO,EAAC,IAAI,EAAE,OAAO,EAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,EAAE,CAAC;QAChB,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { chmod, mkdtemp, rm, writeFile } from \"node:fs/promises\";\nimport { rmSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { Settings } from \"@anthropic-ai/claude-agent-sdk\";\n\nconst directories = new Set<string>();\nlet cleanupRegistered = false;\n\n/** Pass only a pathname in native arguments, never an inline credential-bearing settings object. */\nexport async function privateClaudeConfig(settings: Settings) {\n const directory = await mkdtemp(join(tmpdir(), \"subharness-claude-\"));\n directories.add(directory);\n if (!cleanupRegistered) {\n cleanupRegistered = true;\n process.once(\"exit\", () => {\n for (const owned of directories) {\n try { rmSync(owned, {recursive:true, force:true}); } catch { /* Best effort on process exit. */ }\n }\n });\n }\n const cleanup = async () => {\n await rm(directory, {recursive:true, force:true});\n directories.delete(directory);\n };\n try {\n await chmod(directory, 0o700);\n const path = join(directory, \"settings.json\");\n await writeFile(path, JSON.stringify(settings), {mode:0o600, flag:\"wx\"});\n return {path, cleanup};\n } catch (error) {\n await cleanup();\n throw error;\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A closeable input stream keeps the native harness alive between turns. */
|
|
2
|
+
export declare class ClaudeInput<T> implements AsyncIterable<T> {
|
|
3
|
+
private values;
|
|
4
|
+
private waiting?;
|
|
5
|
+
private ended;
|
|
6
|
+
push(value: T): void;
|
|
7
|
+
close(): void;
|
|
8
|
+
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** A closeable input stream keeps the native harness alive between turns. */
|
|
2
|
+
export class ClaudeInput {
|
|
3
|
+
values = [];
|
|
4
|
+
waiting;
|
|
5
|
+
ended = false;
|
|
6
|
+
push(value) {
|
|
7
|
+
if (this.ended)
|
|
8
|
+
throw new Error("Native input stream is closed.");
|
|
9
|
+
if (this.waiting) {
|
|
10
|
+
const resolve = this.waiting;
|
|
11
|
+
this.waiting = undefined;
|
|
12
|
+
resolve({ value, done: false });
|
|
13
|
+
}
|
|
14
|
+
else
|
|
15
|
+
this.values.push(value);
|
|
16
|
+
}
|
|
17
|
+
close() {
|
|
18
|
+
this.ended = true;
|
|
19
|
+
this.waiting?.({ value: undefined, done: true });
|
|
20
|
+
this.waiting = undefined;
|
|
21
|
+
}
|
|
22
|
+
[Symbol.asyncIterator]() {
|
|
23
|
+
return { next: () => {
|
|
24
|
+
if (this.values.length)
|
|
25
|
+
return Promise.resolve({ value: this.values.shift(), done: false });
|
|
26
|
+
if (this.ended)
|
|
27
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
28
|
+
return new Promise(resolve => { this.waiting = resolve; });
|
|
29
|
+
} };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=claude-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-input.js","sourceRoot":"","sources":["../../src/adapters/claude-input.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,OAAO,WAAW;IACd,MAAM,GAAQ,EAAE,CAAC;IACjB,OAAO,CAAuC;IAC9C,KAAK,GAAG,KAAK,CAAC;IAEtB,IAAI,CAAC,KAAQ;QACX,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;QAClE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,OAAO,CAAC,EAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;QAChC,CAAC;;YAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,EAAE,CAAC,EAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;IAC3B,CAAC;IAED,CAAC,MAAM,CAAC,aAAa,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE;gBAClB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM;oBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAG,EAAE,IAAI,EAAE,KAAK,EAAC,CAAC,CAAC;gBAC3F,IAAI,IAAI,CAAC,KAAK;oBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAC,CAAC,CAAC;gBACvE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,CAAC,EAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["/** A closeable input stream keeps the native harness alive between turns. */\nexport class ClaudeInput<T> implements AsyncIterable<T> {\n private values: T[] = [];\n private waiting?: (result: IteratorResult<T>) => void;\n private ended = false;\n\n push(value: T): void {\n if (this.ended) throw new Error(\"Native input stream is closed.\");\n if (this.waiting) {\n const resolve = this.waiting;\n this.waiting = undefined;\n resolve({value, done: false});\n } else this.values.push(value);\n }\n\n close(): void {\n this.ended = true;\n this.waiting?.({value: undefined, done: true});\n this.waiting = undefined;\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return { next: () => {\n if (this.values.length) return Promise.resolve({value: this.values.shift()!, done: false});\n if (this.ended) return Promise.resolve({value: undefined, done: true});\n return new Promise(resolve => { this.waiting = resolve; });\n }};\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { shellQuote } from "../shell.js";
|
|
2
|
+
import { isAbsolute } from "node:path";
|
|
3
|
+
import { AgentError } from "../errors.js";
|
|
4
|
+
/** Only the native permission evaluator decides whether a shell call is allowed. */
|
|
5
|
+
export function claudeDelegationPermissions(options) {
|
|
6
|
+
const names = Object.keys(options.agent.subagents ?? {});
|
|
7
|
+
if (names.length === 0)
|
|
8
|
+
return [];
|
|
9
|
+
const path = options.env.AGENT_CLI_PATH;
|
|
10
|
+
// Bash rules treat '*' as a wildcard even when it occurs inside shell quotes.
|
|
11
|
+
if (!path || !isAbsolute(path) || /[\x00-\x1f\x7f*]/u.test(path)) {
|
|
12
|
+
throw new AgentError("INVALID_CONFIG", "The session launcher needs an absolute path without wildcards or control characters for Claude Code permissions.");
|
|
13
|
+
}
|
|
14
|
+
const launchers = [shellQuote(path)];
|
|
15
|
+
// Native matching is textual: harmless quote changes need exact separate rules.
|
|
16
|
+
if (/^[A-Za-z0-9_./-]+$/u.test(path))
|
|
17
|
+
launchers.push(path, `"${path}"`);
|
|
18
|
+
const commands = [
|
|
19
|
+
...names.map(name => `run subagent:${name}`),
|
|
20
|
+
"list", "--help", "send", "wait", "status", "queue", "cancel", "resume",
|
|
21
|
+
];
|
|
22
|
+
return commands.flatMap(command => launchers.map(launcher => `Bash(${launcher} ${command} *)`));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=claude-permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-permissions.js","sourceRoot":"","sources":["../../src/adapters/claude-permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,oFAAoF;AACpF,MAAM,UAAU,2BAA2B,CAAC,OAAuB;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IACxC,8EAA8E;IAC9E,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,kHAAkH,CAAC,CAAC;IAC7J,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACrC,gFAAgF;IAChF,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG;QACf,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,IAAI,EAAE,CAAC;QAC5C,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ;KACxE,CAAC;IACF,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,QAAQ,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC;AAClG,CAAC","sourcesContent":["import type { AdapterOptions } from \"./types.js\";\nimport { shellQuote } from \"../shell.js\";\nimport { isAbsolute } from \"node:path\";\nimport { AgentError } from \"../errors.js\";\n\n/** Only the native permission evaluator decides whether a shell call is allowed. */\nexport function claudeDelegationPermissions(options: AdapterOptions): string[] {\n const names = Object.keys(options.agent.subagents ?? {});\n if (names.length === 0) return [];\n const path = options.env.AGENT_CLI_PATH;\n // Bash rules treat '*' as a wildcard even when it occurs inside shell quotes.\n if (!path || !isAbsolute(path) || /[\\x00-\\x1f\\x7f*]/u.test(path)) {\n throw new AgentError(\"INVALID_CONFIG\", \"The session launcher needs an absolute path without wildcards or control characters for Claude Code permissions.\");\n }\n const launchers = [shellQuote(path)];\n // Native matching is textual: harmless quote changes need exact separate rules.\n if (/^[A-Za-z0-9_./-]+$/u.test(path)) launchers.push(path, `\"${path}\"`);\n const commands = [\n ...names.map(name => `run subagent:${name}`),\n \"list\", \"--help\", \"send\", \"wait\", \"status\", \"queue\", \"cancel\", \"resume\",\n ];\n return commands.flatMap(command => launchers.map(launcher => `Bash(${launcher} ${command} *)`));\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
export interface ClaudeSettingsControl {
|
|
3
|
+
getSettings?(): Promise<unknown>;
|
|
4
|
+
setModel?(model?: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export type AppliedClaudeSettings = {
|
|
7
|
+
model: string;
|
|
8
|
+
effort: string | null;
|
|
9
|
+
};
|
|
10
|
+
export declare class ClaudeSettingsUnavailable extends Error {
|
|
11
|
+
}
|
|
12
|
+
export declare function readClaudeAppliedSettings(native: ClaudeSettingsControl): Promise<AppliedClaudeSettings>;
|
|
13
|
+
export declare function verifyAppliedClaudeEffort(applied: string | null, requested: string | undefined): void;
|
|
14
|
+
export declare function claudeDefaultUnavailable(): AgentError;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AgentError } from "../errors.js";
|
|
2
|
+
export class ClaudeSettingsUnavailable extends Error {
|
|
3
|
+
}
|
|
4
|
+
function isUnsupportedSettingsControl(error) {
|
|
5
|
+
if (!(error instanceof Error))
|
|
6
|
+
return false;
|
|
7
|
+
return /^(?:unknown|unsupported) control request(?: subtype)?:\s*get_settings\.?$/iu.test(error.message.trim());
|
|
8
|
+
}
|
|
9
|
+
export async function readClaudeAppliedSettings(native) {
|
|
10
|
+
if (!native.getSettings)
|
|
11
|
+
throw new ClaudeSettingsUnavailable();
|
|
12
|
+
let value;
|
|
13
|
+
try {
|
|
14
|
+
value = await native.getSettings();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (isUnsupportedSettingsControl(error))
|
|
18
|
+
throw new ClaudeSettingsUnavailable();
|
|
19
|
+
throw new AgentError("INVALID_CONFIG", "Claude Code settings control failed before submission.");
|
|
20
|
+
}
|
|
21
|
+
const applied = value && typeof value === "object" && !Array.isArray(value)
|
|
22
|
+
? value.applied : undefined;
|
|
23
|
+
if (applied === undefined)
|
|
24
|
+
throw new ClaudeSettingsUnavailable();
|
|
25
|
+
if (!applied || typeof applied !== "object" || Array.isArray(applied)) {
|
|
26
|
+
throw new AgentError("PROTOCOL_ERROR", "Claude Code returned malformed applied settings.");
|
|
27
|
+
}
|
|
28
|
+
const model = applied.model;
|
|
29
|
+
const effort = applied.effort;
|
|
30
|
+
if (typeof model !== "string" || model.length === 0 || (effort !== null && effort !== undefined && typeof effort !== "string")) {
|
|
31
|
+
throw new AgentError("PROTOCOL_ERROR", "Claude Code returned malformed applied settings.");
|
|
32
|
+
}
|
|
33
|
+
return { model, effort: effort === undefined ? null : effort };
|
|
34
|
+
}
|
|
35
|
+
export function verifyAppliedClaudeEffort(applied, requested) {
|
|
36
|
+
if (requested !== undefined && applied !== requested) {
|
|
37
|
+
throw new AgentError("UNSUPPORTED_OPTION", "Claude Code did not apply the explicitly selected effort.");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function claudeDefaultUnavailable() {
|
|
41
|
+
return new AgentError("INVALID_CONFIG", "This Claude Code/SDK combination cannot verify its native default before submission. Supply --model <model>.");
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=claude-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/adapters/claude-settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAS1C,MAAM,OAAO,yBAA0B,SAAQ,KAAK;CAAG;AAEvD,SAAS,4BAA4B,CAAC,KAAc;IAClD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,6EAA6E,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAClH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,MAA6B;IAC3E,IAAI,CAAC,MAAM,CAAC,WAAW;QAAE,MAAM,IAAI,yBAAyB,EAAE,CAAC;IAC/D,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QAAC,KAAK,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;IAAC,CAAC;IAC3C,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,4BAA4B,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,yBAAyB,EAAE,CAAC;QAC/E,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,wDAAwD,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzE,CAAC,CAAE,KAA6B,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,IAAI,yBAAyB,EAAE,CAAC;IACjE,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,kDAAkD,CAAC,CAAC;IAC7F,CAAC;IACD,MAAM,KAAK,GAAI,OAA6B,CAAC,KAAK,CAAC;IACnD,MAAM,MAAM,GAAI,OAA8B,CAAC,MAAM,CAAC;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAE,CAAC;QAC/H,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,kDAAkD,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,EAAC,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAuB,EAAC,CAAC;AAChF,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAAsB,EAAE,SAA6B;IAC7F,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACrD,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,2DAA2D,CAAC,CAAC;IAC1G,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,IAAI,UAAU,CAAC,gBAAgB,EAAE,8GAA8G,CAAC,CAAC;AAC1J,CAAC","sourcesContent":["import { AgentError } from \"../errors.js\";\n\nexport interface ClaudeSettingsControl {\n getSettings?(): Promise<unknown>;\n setModel?(model?: string): Promise<void>;\n}\n\nexport type AppliedClaudeSettings = {model: string; effort: string | null};\n\nexport class ClaudeSettingsUnavailable extends Error {}\n\nfunction isUnsupportedSettingsControl(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n return /^(?:unknown|unsupported) control request(?: subtype)?:\\s*get_settings\\.?$/iu.test(error.message.trim());\n}\n\nexport async function readClaudeAppliedSettings(native: ClaudeSettingsControl): Promise<AppliedClaudeSettings> {\n if (!native.getSettings) throw new ClaudeSettingsUnavailable();\n let value: unknown;\n try { value = await native.getSettings(); }\n catch (error) {\n if (isUnsupportedSettingsControl(error)) throw new ClaudeSettingsUnavailable();\n throw new AgentError(\"INVALID_CONFIG\", \"Claude Code settings control failed before submission.\");\n }\n const applied = value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as {applied?: unknown}).applied : undefined;\n if (applied === undefined) throw new ClaudeSettingsUnavailable();\n if (!applied || typeof applied !== \"object\" || Array.isArray(applied)) {\n throw new AgentError(\"PROTOCOL_ERROR\", \"Claude Code returned malformed applied settings.\");\n }\n const model = (applied as {model?: unknown}).model;\n const effort = (applied as {effort?: unknown}).effort;\n if (typeof model !== \"string\" || model.length === 0 || (effort !== null && effort !== undefined && typeof effort !== \"string\")) {\n throw new AgentError(\"PROTOCOL_ERROR\", \"Claude Code returned malformed applied settings.\");\n }\n return {model, effort: effort === undefined ? null : effort as string | null};\n}\n\nexport function verifyAppliedClaudeEffort(applied: string | null, requested: string | undefined): void {\n if (requested !== undefined && applied !== requested) {\n throw new AgentError(\"UNSUPPORTED_OPTION\", \"Claude Code did not apply the explicitly selected effort.\");\n }\n}\n\nexport function claudeDefaultUnavailable(): AgentError {\n return new AgentError(\"INVALID_CONFIG\", \"This Claude Code/SDK combination cannot verify its native default before submission. Supply --model <model>.\");\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Options } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { ExecutionDefinition } from "./types.js";
|
|
3
|
+
/** The native process may stop before host-side JavaScript callbacks finish. */
|
|
4
|
+
export declare function claudeTools(agent: Pick<ExecutionDefinition, "tools">): {
|
|
5
|
+
config: Pick<Options, "allowedTools" | "mcpServers">;
|
|
6
|
+
start(): void;
|
|
7
|
+
stopAdmission(): void;
|
|
8
|
+
drain(): Promise<void>;
|
|
9
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
+
import { AgentError } from "../errors.js";
|
|
4
|
+
import { executeTool, toolJsonSchema } from "../sdk/tools.js";
|
|
5
|
+
import { mcpToolContent } from "./mcp-tool-content.js";
|
|
6
|
+
/** The native process may stop before host-side JavaScript callbacks finish. */
|
|
7
|
+
export function claudeTools(agent) {
|
|
8
|
+
const pending = new Set();
|
|
9
|
+
let accepting = false;
|
|
10
|
+
const definitions = Object.entries(agent.tools ?? {});
|
|
11
|
+
const namespace = `subharness_${randomUUID().replaceAll("-", "")}`;
|
|
12
|
+
const tools = definitions.map(([name, definition]) => {
|
|
13
|
+
toolJsonSchema(definition);
|
|
14
|
+
return tool(name, definition.description, definition.inputSchema.shape, async (input) => {
|
|
15
|
+
if (!accepting)
|
|
16
|
+
return errorResult("The task is not accepting tool calls.");
|
|
17
|
+
const execution = executeTool(definition, input);
|
|
18
|
+
pending.add(execution);
|
|
19
|
+
try {
|
|
20
|
+
return { content: mcpToolContent(await execution) };
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return errorResult(error instanceof AgentError ? error.message : "The custom tool failed.");
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
pending.delete(execution);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
const config = definitions.length === 0 ? {} : {
|
|
31
|
+
mcpServers: { [namespace]: createSdkMcpServer({ name: namespace, version: "1.0.0", tools }) },
|
|
32
|
+
allowedTools: definitions.map(([name]) => `mcp__${namespace}__${name}`),
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
config,
|
|
36
|
+
start() { accepting = true; },
|
|
37
|
+
stopAdmission() { accepting = false; },
|
|
38
|
+
async drain() {
|
|
39
|
+
accepting = false;
|
|
40
|
+
await Promise.allSettled([...pending]);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function errorResult(text) {
|
|
45
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=claude-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-tools.js","sourceRoot":"","sources":["../../src/adapters/claude-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAgB,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAC,KAAyC;IACnE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,cAAc,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;IACnE,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,EAAE;QACnD,cAAc,CAAC,UAAU,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YACpF,IAAI,CAAC,SAAS;gBAAE,OAAO,WAAW,CAAC,uCAAuC,CAAC,CAAC;YAC5E,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACjD,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACvB,IAAI,CAAC;gBACH,OAAO,EAAC,OAAO,EAAE,cAAc,CAAC,MAAM,SAAS,CAAC,EAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,WAAW,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC;YAC9F,CAAC;oBAAS,CAAC;gBACT,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAiD,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,UAAU,EAAE,EAAC,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC,EAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAC,CAAC,EAAC;QACzF,YAAY,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,QAAQ,SAAS,KAAK,IAAI,EAAE,CAAC;KACxE,CAAC;IACF,OAAO;QACL,MAAM;QACN,KAAK,KAAK,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC;QAC7B,aAAa,KAAK,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,KAAK;YACT,SAAS,GAAG,KAAK,CAAC;YAClB,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,EAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAC,IAAI,EAAE,MAAe,EAAE,IAAI,EAAC,CAAC,EAAC,CAAC;AACnE,CAAC","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { createSdkMcpServer, tool, type Options } from \"@anthropic-ai/claude-agent-sdk\";\nimport { AgentError } from \"../errors.js\";\nimport { executeTool, toolJsonSchema } from \"../sdk/tools.js\";\nimport type { ExecutionDefinition } from \"./types.js\";\nimport { mcpToolContent } from \"./mcp-tool-content.js\";\n\n/** The native process may stop before host-side JavaScript callbacks finish. */\nexport function claudeTools(agent: Pick<ExecutionDefinition, \"tools\">) {\n const pending = new Set<Promise<unknown>>();\n let accepting = false;\n const definitions = Object.entries(agent.tools ?? {});\n const namespace = `subharness_${randomUUID().replaceAll(\"-\", \"\")}`;\n const tools = definitions.map(([name, definition]) => {\n toolJsonSchema(definition);\n return tool(name, definition.description, definition.inputSchema.shape, async input => {\n if (!accepting) return errorResult(\"The task is not accepting tool calls.\");\n const execution = executeTool(definition, input);\n pending.add(execution);\n try {\n return {content: mcpToolContent(await execution)};\n } catch (error) {\n return errorResult(error instanceof AgentError ? error.message : \"The custom tool failed.\");\n } finally {\n pending.delete(execution);\n }\n });\n });\n const config: Pick<Options, \"mcpServers\" | \"allowedTools\"> = definitions.length === 0 ? {} : {\n mcpServers: {[namespace]: createSdkMcpServer({name: namespace, version: \"1.0.0\", tools})},\n allowedTools: definitions.map(([name]) => `mcp__${namespace}__${name}`),\n };\n return {\n config,\n start() { accepting = true; },\n stopAdmission() { accepting = false; },\n async drain() {\n accepting = false;\n await Promise.allSettled([...pending]);\n },\n };\n}\n\nfunction errorResult(text: string) {\n return {isError: true, content: [{type: \"text\" as const, text}]};\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type AccountInfo, type Options, type SDKMessage, type SDKUserMessage, type Settings, type ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import type { AdapterOptions, NativeSession } from "./types.js";
|
|
3
|
+
interface ClaudeQuery extends AsyncIterable<SDKMessage> {
|
|
4
|
+
accountInfo(): Promise<AccountInfo>;
|
|
5
|
+
supportedModels(): Promise<ModelInfo[]>;
|
|
6
|
+
interrupt(): Promise<unknown>;
|
|
7
|
+
close(): void;
|
|
8
|
+
getSettings?(): Promise<unknown>;
|
|
9
|
+
setModel?(model?: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface ClaudeDependencies {
|
|
12
|
+
query(args: {
|
|
13
|
+
prompt: AsyncIterable<SDKUserMessage>;
|
|
14
|
+
options: Options;
|
|
15
|
+
}): ClaudeQuery;
|
|
16
|
+
executable(env: NodeJS.ProcessEnv): Promise<string>;
|
|
17
|
+
managedSettings(cwd: string): Promise<Settings[]>;
|
|
18
|
+
}
|
|
19
|
+
export declare const openClaude: (options: AdapterOptions) => Promise<NativeSession>;
|
|
20
|
+
/** Dependency injection is internal and replaces only the external SDK boundary in tests. */
|
|
21
|
+
export declare function createClaudeOpener(deps: ClaudeDependencies): (options: AdapterOptions) => Promise<NativeSession>;
|
|
22
|
+
export {};
|