@amykit/plugin-claude 0.2.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/README.md +56 -0
- package/dist/ClaudeHarness.d.ts +30 -0
- package/dist/ClaudeHarness.d.ts.map +1 -0
- package/dist/ClaudeHarness.js +65 -0
- package/dist/ClaudeHarness.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +68 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +90 -0
- package/dist/envelope.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +30 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!-- Generated by `npm run docs:generate` from the package itself. Do not edit by hand. -->
|
|
2
|
+
|
|
3
|
+
# @amykit/plugin-claude
|
|
4
|
+
|
|
5
|
+
The claude CLI as the agent, with git on the side.
|
|
6
|
+
|
|
7
|
+
A plugin for [amy](https://github.com/nicolasmelo1/amy). It provides `claude` in the `agent` collection and `claude` in the `harness` collection.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install -g @amykit/plugin-claude
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then name it in `~/.amy/config.yaml`:
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
workflows:
|
|
19
|
+
your-profile:
|
|
20
|
+
plugins:
|
|
21
|
+
- "@amykit/plugin-claude"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
plugins:
|
|
28
|
+
"@amykit/plugin-claude":
|
|
29
|
+
defaultBranch: "main"
|
|
30
|
+
model: ""
|
|
31
|
+
models: []
|
|
32
|
+
reviewerHints: {}
|
|
33
|
+
timeoutMs: 1800000
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Setting | Type | Required | Default | What it is |
|
|
37
|
+
| :-- | :-- | :-- | :-- | :-- |
|
|
38
|
+
| `defaultBranch` | `string` | no | `main` | the branch new work is cut from, which is not always `main` |
|
|
39
|
+
| `model` | `string` | no | `""` | passed to the CLI as --model, which is the flag it accepts |
|
|
40
|
+
| `models` | `string[]` | no | `[]` | the model tiers to offer the relay, cheapest first. One agent is contributed per tier, named `claude:<model>`. Empty means a single agent using `model` |
|
|
41
|
+
| `reviewerHints` | `record` | no | `{}` | guidance appended when answering a particular reviewer, by host login |
|
|
42
|
+
| `timeoutMs` | `number` | no | `1800000` | how long one agent call may run before it is given up on |
|
|
43
|
+
|
|
44
|
+
Every field is checked at boot against the schema this package declares. A key that is
|
|
45
|
+
not one of the above is a refusal naming the plugin and the key, not a setting that
|
|
46
|
+
silently never applied.
|
|
47
|
+
|
|
48
|
+
## Where the reasoning is
|
|
49
|
+
|
|
50
|
+
- [Writing a plugin](https://github.com/nicolasmelo1/amy/tree/main/docs/build/write-a-plugin.md)
|
|
51
|
+
- [Reference: plugins](https://github.com/nicolasmelo1/amy/tree/main/docs/reference/plugins.md)
|
|
52
|
+
- [Ports and the registry](https://github.com/nicolasmelo1/amy/tree/main/docs/concepts/ports.md)
|
|
53
|
+
|
|
54
|
+
## Licence
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CommandRunner, Harness, HarnessReply } from "@amykit/core";
|
|
2
|
+
export interface ClaudeHarnessConfig {
|
|
3
|
+
/** Passed to the CLI as --model. Omit to use whatever the CLI defaults to. */
|
|
4
|
+
model?: string;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The `claude` CLI, and everything knowable about what a call to it took.
|
|
9
|
+
*
|
|
10
|
+
* `--output-format json` is what makes the run measurable rather than
|
|
11
|
+
* guessed: it returns the answer, the token counts, the cost the harness
|
|
12
|
+
* itself worked out, and an error status to classify on.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ClaudeHarness implements Harness {
|
|
15
|
+
private readonly runner;
|
|
16
|
+
private readonly config;
|
|
17
|
+
readonly name = "claude";
|
|
18
|
+
constructor(runner: CommandRunner, config?: ClaudeHarnessConfig);
|
|
19
|
+
ask(prompt: string, cwd: string): Promise<HarnessReply>;
|
|
20
|
+
/**
|
|
21
|
+
* Turns a parsed run into the account the engine logs and the budget spends.
|
|
22
|
+
*
|
|
23
|
+
* A cost the harness reported wins, because it knows the plan and the
|
|
24
|
+
* discounts. Failing that, the vendored table computes one and says so. A
|
|
25
|
+
* model the table has never heard of leaves the cost absent rather than
|
|
26
|
+
* inventing a rate to spend against a ceiling.
|
|
27
|
+
*/
|
|
28
|
+
private account;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=ClaudeHarness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeHarness.d.ts","sourceRoot":"","sources":["../src/ClaudeHarness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,aAAa,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAI9E,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,OAAO;IAIzC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,QAAQ,CAAC,IAAI,YAAY;gBAGN,MAAM,EAAE,aAAa,EACrB,MAAM,GAAE,mBAAwB;IAG7C,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAoB7D;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;CAuBhB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { costOf, specFor } from "@amykit/model-specs";
|
|
2
|
+
import { parseRun } from "./envelope.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `claude` CLI, and everything knowable about what a call to it took.
|
|
5
|
+
*
|
|
6
|
+
* `--output-format json` is what makes the run measurable rather than
|
|
7
|
+
* guessed: it returns the answer, the token counts, the cost the harness
|
|
8
|
+
* itself worked out, and an error status to classify on.
|
|
9
|
+
*/
|
|
10
|
+
export class ClaudeHarness {
|
|
11
|
+
runner;
|
|
12
|
+
config;
|
|
13
|
+
name = "claude";
|
|
14
|
+
constructor(runner, config = {}) {
|
|
15
|
+
this.runner = runner;
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
async ask(prompt, cwd) {
|
|
19
|
+
const asked = this.config.model ?? "";
|
|
20
|
+
const args = [];
|
|
21
|
+
if (this.config.model) {
|
|
22
|
+
args.push("--model", this.config.model);
|
|
23
|
+
}
|
|
24
|
+
args.push("--output-format", "json", "-p");
|
|
25
|
+
const started = Date.now();
|
|
26
|
+
const result = await this.runner.run("claude", args, {
|
|
27
|
+
cwd,
|
|
28
|
+
stdin: prompt,
|
|
29
|
+
timeoutMs: this.config.timeoutMs,
|
|
30
|
+
});
|
|
31
|
+
const elapsed = Date.now() - started;
|
|
32
|
+
const parsed = parseRun(result.stdout, result.ok, asked);
|
|
33
|
+
return { text: parsed.text, run: this.account(parsed, elapsed, result.stderr) };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Turns a parsed run into the account the engine logs and the budget spends.
|
|
37
|
+
*
|
|
38
|
+
* A cost the harness reported wins, because it knows the plan and the
|
|
39
|
+
* discounts. Failing that, the vendored table computes one and says so. A
|
|
40
|
+
* model the table has never heard of leaves the cost absent rather than
|
|
41
|
+
* inventing a rate to spend against a ceiling.
|
|
42
|
+
*/
|
|
43
|
+
account(parsed, elapsedMs, stderr) {
|
|
44
|
+
let costUsd = parsed.costUsd;
|
|
45
|
+
let costSource = parsed.costSource;
|
|
46
|
+
if (costUsd === undefined && parsed.tokens) {
|
|
47
|
+
const spec = specFor(parsed.model);
|
|
48
|
+
if (spec) {
|
|
49
|
+
costUsd = costOf(spec, parsed.tokens);
|
|
50
|
+
costSource = "computed";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
outcome: parsed.outcome,
|
|
55
|
+
harness: this.name,
|
|
56
|
+
model: parsed.model,
|
|
57
|
+
tokens: parsed.tokens,
|
|
58
|
+
costUsd,
|
|
59
|
+
costSource,
|
|
60
|
+
durationMs: parsed.durationMs ?? elapsedMs,
|
|
61
|
+
output: [parsed.text, stderr].filter(Boolean).join("\n").trim(),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=ClaudeHarness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClaudeHarness.js","sourceRoot":"","sources":["../src/ClaudeHarness.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAa,QAAQ,EAAE,MAAM,eAAe,CAAC;AAQpD;;;;;;GAMG;AACH,MAAM,OAAO,aAAa;IAIL;IACA;IAJV,IAAI,GAAG,QAAQ,CAAC;IAEzB,YACmB,MAAqB,EACrB,SAA8B,EAAE;QADhC,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAA0B;IAChD,CAAC;IAEJ,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,GAAW;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE;YACnD,GAAG;YACH,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;SACjC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QAErC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACzD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IAClF,CAAC;IAED;;;;;;;OAOG;IACK,OAAO,CAAC,MAAiB,EAAE,SAAiB,EAAE,MAAc;QAClE,IAAI,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC7B,IAAI,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAEnC,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,IAAI,EAAE,CAAC;gBACT,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;gBACtC,UAAU,GAAG,UAAU,CAAC;YAC1B,CAAC;QACH,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,OAAO,EAAE,IAAI,CAAC,IAAI;YAClB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO;YACP,UAAU;YACV,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;YAC1C,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;SAChE,CAAC;IACJ,CAAC;CACF"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,2DAA2D;AAC3D,eAAO,MAAM,YAAY,EAAE,YA2B1B,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** What this plugin needs told to it, and nothing more. */
|
|
2
|
+
export const configSchema = {
|
|
3
|
+
defaultBranch: {
|
|
4
|
+
type: "string",
|
|
5
|
+
description: "the branch new work is cut from, which is not always `main`",
|
|
6
|
+
default: "main",
|
|
7
|
+
},
|
|
8
|
+
model: {
|
|
9
|
+
type: "string",
|
|
10
|
+
description: "passed to the CLI as --model, which is the flag it accepts",
|
|
11
|
+
default: "",
|
|
12
|
+
},
|
|
13
|
+
models: {
|
|
14
|
+
type: "string[]",
|
|
15
|
+
description: "the model tiers to offer the relay, cheapest first. One agent is contributed per tier, named `claude:<model>`. Empty means a single agent using `model`",
|
|
16
|
+
default: [],
|
|
17
|
+
},
|
|
18
|
+
reviewerHints: {
|
|
19
|
+
type: "record",
|
|
20
|
+
description: "guidance appended when answering a particular reviewer, by host login",
|
|
21
|
+
default: {},
|
|
22
|
+
},
|
|
23
|
+
timeoutMs: {
|
|
24
|
+
type: "number",
|
|
25
|
+
description: "how long one agent call may run before it is given up on",
|
|
26
|
+
default: 30 * 60 * 1000,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,2DAA2D;AAC3D,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,aAAa,EAAE;QACb,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,6DAA6D;QAC1E,OAAO,EAAE,MAAM;KAChB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,4DAA4D;QACzE,OAAO,EAAE,EAAE;KACZ;IACD,MAAM,EAAE;QACN,IAAI,EAAE,UAAU;QAChB,WAAW,EACT,yJAAyJ;QAC3J,OAAO,EAAE,EAAE;KACZ;IACD,aAAa,EAAE;QACb,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,uEAAuE;QACpF,OAAO,EAAE,EAAE;KACZ;IACD,SAAS,EAAE;QACT,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,0DAA0D;QACvE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;KACxB;CACF,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { AgentOutcome, CostSource, TokenUsage } from "@amykit/core";
|
|
2
|
+
/** The shape `claude -p --output-format json` prints. */
|
|
3
|
+
export interface ClaudeEnvelope {
|
|
4
|
+
result?: string;
|
|
5
|
+
is_error?: boolean;
|
|
6
|
+
api_error_status?: number | string | null;
|
|
7
|
+
stop_reason?: string | null;
|
|
8
|
+
total_cost_usd?: number;
|
|
9
|
+
duration_ms?: number;
|
|
10
|
+
usage?: {
|
|
11
|
+
input_tokens?: number;
|
|
12
|
+
output_tokens?: number;
|
|
13
|
+
cache_read_input_tokens?: number;
|
|
14
|
+
cache_creation_input_tokens?: number;
|
|
15
|
+
/**
|
|
16
|
+
* How the cache writes split by time to live.
|
|
17
|
+
*
|
|
18
|
+
* This is present in real envelopes and matters: a one-hour write is
|
|
19
|
+
* billed at twice the input rate, so ignoring the split undercounts a
|
|
20
|
+
* cache-heavy run badly. One real run had all 10,232 of its cache writes
|
|
21
|
+
* at one hour, which is a 60% difference in the computed cost.
|
|
22
|
+
*/
|
|
23
|
+
cache_creation?: {
|
|
24
|
+
ephemeral_1h_input_tokens?: number;
|
|
25
|
+
ephemeral_5m_input_tokens?: number;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
modelUsage?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface ParsedRun {
|
|
31
|
+
outcome: AgentOutcome;
|
|
32
|
+
text: string;
|
|
33
|
+
model: string;
|
|
34
|
+
tokens?: TokenUsage;
|
|
35
|
+
costUsd?: number;
|
|
36
|
+
costSource: CostSource;
|
|
37
|
+
durationMs?: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads the envelope, or says it could not.
|
|
41
|
+
*
|
|
42
|
+
* Returns null rather than throwing, because a harness that printed something
|
|
43
|
+
* unexpected is a run to classify, not an exception to unwind.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseEnvelope(stdout: string): ClaudeEnvelope | null;
|
|
46
|
+
/**
|
|
47
|
+
* Which of the four outcomes this run was.
|
|
48
|
+
*
|
|
49
|
+
* Read off the envelope, not off stderr. A rate limit and a failure want
|
|
50
|
+
* opposite responses, and guessing between them from log text is how a relay
|
|
51
|
+
* ends up escalating to an expensive model over a quota problem.
|
|
52
|
+
*
|
|
53
|
+
* Known limit: a rate limit severe enough that no envelope is printed
|
|
54
|
+
* classifies as `failed`. That is honest rather than clever — when a real
|
|
55
|
+
* throttled envelope is in hand, its shape goes here.
|
|
56
|
+
*/
|
|
57
|
+
export declare function classify(envelope: ClaudeEnvelope | null, exitOk: boolean): AgentOutcome;
|
|
58
|
+
/** The token counts, if the envelope carried them. */
|
|
59
|
+
export declare function tokensOf(envelope: ClaudeEnvelope | null): TokenUsage | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* The model the run actually used.
|
|
62
|
+
*
|
|
63
|
+
* `modelUsage` is keyed by it, decorations and all, which is more truthful
|
|
64
|
+
* than what was asked for: a fallback would have changed it.
|
|
65
|
+
*/
|
|
66
|
+
export declare function modelOf(envelope: ClaudeEnvelope | null, asked: string): string;
|
|
67
|
+
export declare function parseRun(stdout: string, exitOk: boolean, asked: string): ParsedRun;
|
|
68
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEpE,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE;QACN,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;QACrC;;;;;;;WAOG;QACH,cAAc,CAAC,EAAE;YACf,yBAAyB,CAAC,EAAE,MAAM,CAAC;YACnC,yBAAyB,CAAC,EAAE,MAAM,CAAC;SACpC,CAAC;KACH,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,YAAY,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CASnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,YAAY,CAOvF;AAQD,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,UAAU,GAAG,SAAS,CAahF;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9E;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAqBlF"}
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the envelope, or says it could not.
|
|
3
|
+
*
|
|
4
|
+
* Returns null rather than throwing, because a harness that printed something
|
|
5
|
+
* unexpected is a run to classify, not an exception to unwind.
|
|
6
|
+
*/
|
|
7
|
+
export function parseEnvelope(stdout) {
|
|
8
|
+
const trimmed = stdout.trim();
|
|
9
|
+
if (!trimmed.startsWith("{"))
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(trimmed);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Which of the four outcomes this run was.
|
|
20
|
+
*
|
|
21
|
+
* Read off the envelope, not off stderr. A rate limit and a failure want
|
|
22
|
+
* opposite responses, and guessing between them from log text is how a relay
|
|
23
|
+
* ends up escalating to an expensive model over a quota problem.
|
|
24
|
+
*
|
|
25
|
+
* Known limit: a rate limit severe enough that no envelope is printed
|
|
26
|
+
* classifies as `failed`. That is honest rather than clever — when a real
|
|
27
|
+
* throttled envelope is in hand, its shape goes here.
|
|
28
|
+
*/
|
|
29
|
+
export function classify(envelope, exitOk) {
|
|
30
|
+
if (!envelope)
|
|
31
|
+
return exitOk ? "completed" : "abandoned";
|
|
32
|
+
if (isRateLimit(envelope.api_error_status))
|
|
33
|
+
return "rate-limited";
|
|
34
|
+
if (envelope.is_error === true || !exitOk)
|
|
35
|
+
return "failed";
|
|
36
|
+
return "completed";
|
|
37
|
+
}
|
|
38
|
+
function isRateLimit(status) {
|
|
39
|
+
if (status === undefined || status === null)
|
|
40
|
+
return false;
|
|
41
|
+
if (typeof status === "number")
|
|
42
|
+
return status === 429;
|
|
43
|
+
return status.includes("429");
|
|
44
|
+
}
|
|
45
|
+
/** The token counts, if the envelope carried them. */
|
|
46
|
+
export function tokensOf(envelope) {
|
|
47
|
+
const usage = envelope?.usage;
|
|
48
|
+
if (!usage)
|
|
49
|
+
return undefined;
|
|
50
|
+
const oneHour = usage.cache_creation?.ephemeral_1h_input_tokens;
|
|
51
|
+
return {
|
|
52
|
+
input: usage.input_tokens ?? 0,
|
|
53
|
+
output: usage.output_tokens ?? 0,
|
|
54
|
+
cacheRead: usage.cache_read_input_tokens ?? 0,
|
|
55
|
+
cacheWrite: usage.cache_creation_input_tokens ?? 0,
|
|
56
|
+
...(oneHour === undefined ? {} : { cacheWrite1h: oneHour }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The model the run actually used.
|
|
61
|
+
*
|
|
62
|
+
* `modelUsage` is keyed by it, decorations and all, which is more truthful
|
|
63
|
+
* than what was asked for: a fallback would have changed it.
|
|
64
|
+
*/
|
|
65
|
+
export function modelOf(envelope, asked) {
|
|
66
|
+
const keys = Object.keys(envelope?.modelUsage ?? {});
|
|
67
|
+
return keys[0] ?? asked;
|
|
68
|
+
}
|
|
69
|
+
export function parseRun(stdout, exitOk, asked) {
|
|
70
|
+
const envelope = parseEnvelope(stdout);
|
|
71
|
+
const tokens = tokensOf(envelope);
|
|
72
|
+
const reported = envelope?.total_cost_usd;
|
|
73
|
+
return {
|
|
74
|
+
outcome: classify(envelope, exitOk),
|
|
75
|
+
// The envelope puts the answer in `result`; without one, the raw output
|
|
76
|
+
// is all there is.
|
|
77
|
+
text: envelope?.result ?? stdout,
|
|
78
|
+
model: modelOf(envelope, asked),
|
|
79
|
+
tokens,
|
|
80
|
+
costUsd: typeof reported === "number" ? reported : undefined,
|
|
81
|
+
// The harness knows the plan and the discounts, so what it says beats
|
|
82
|
+
// anything worked out from a price table.
|
|
83
|
+
// Only ever `reported` or `unknown` here. Working a cost out from a
|
|
84
|
+
// price table is the caller's job, and claiming `computed` without
|
|
85
|
+
// computing would be the exact confusion `costSource` exists to prevent.
|
|
86
|
+
costSource: typeof reported === "number" ? "reported" : "unknown",
|
|
87
|
+
durationMs: envelope?.duration_ms,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ClaudeHarness } from "./ClaudeHarness.js";
|
|
2
|
+
export type { ClaudeHarnessConfig } from "./ClaudeHarness.js";
|
|
3
|
+
export { classify, modelOf, parseEnvelope, parseRun, tokensOf } from "./envelope.js";
|
|
4
|
+
export type { ClaudeEnvelope, ParsedRun } from "./envelope.js";
|
|
5
|
+
export { configSchema } from "./config.js";
|
|
6
|
+
export { plugin } from "./plugin.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACrF,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAErF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,MAAM,EAAE,MAAM,cAAc,CAAC;AAK3C,eAAO,MAAM,MAAM,EAAE,MA2BpB,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Git } from "@amykit/core";
|
|
2
|
+
import { contributeTiers } from "@amykit/agent-kit";
|
|
3
|
+
import { ClaudeHarness } from "./ClaudeHarness.js";
|
|
4
|
+
import { configSchema } from "./config.js";
|
|
5
|
+
export const plugin = {
|
|
6
|
+
name: "@amykit/plugin-claude",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
configSchema,
|
|
9
|
+
register(registry, ctx) {
|
|
10
|
+
const git = new Git(ctx.runner, {
|
|
11
|
+
workspaceRoot: ctx.paths.workspace,
|
|
12
|
+
defaultBranch: ctx.config.defaultBranch,
|
|
13
|
+
});
|
|
14
|
+
const tiers = ctx.config.models;
|
|
15
|
+
// Contributed rather than mounted as the `agent` port: only one plugin
|
|
16
|
+
// can own a port, and three harnesses that each wanted to be *the* agent
|
|
17
|
+
// would refuse to mount together. The relay composes what is contributed.
|
|
18
|
+
contributeTiers(registry, {
|
|
19
|
+
harness: "claude",
|
|
20
|
+
models: tiers.length > 0 ? tiers : [ctx.config.model || ""],
|
|
21
|
+
git,
|
|
22
|
+
agent: { reviewerHints: ctx.config.reviewerHints },
|
|
23
|
+
make: (model) => new ClaudeHarness(ctx.runner, {
|
|
24
|
+
model: model || undefined,
|
|
25
|
+
timeoutMs: ctx.config.timeoutMs,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAU,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,MAAM,GAAW;IAC5B,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;IAChB,YAAY;IACZ,QAAQ,CAAC,QAAQ,EAAE,GAAG;QACpB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE;YAC9B,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS;YAClC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,aAAuB;SAClD,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,MAAkB,CAAC;QAE5C,uEAAuE;QACvE,yEAAyE;QACzE,0EAA0E;QAC1E,eAAe,CAAC,QAAQ,EAAE;YACxB,OAAO,EAAE,QAAQ;YACjB,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,MAAM,CAAC,KAAgB,IAAI,EAAE,CAAC;YACvE,GAAG;YACH,KAAK,EAAE,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,aAAuC,EAAE;YAC5E,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACd,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE;gBAC5B,KAAK,EAAE,KAAK,IAAI,SAAS;gBACzB,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAmB;aAC1C,CAAC;SACL,CAAC,CAAC;IACL,CAAC;CACF,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amykit/plugin-claude",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The claude CLI as the agent, with git on the side.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Nicolas Melo",
|
|
8
|
+
"homepage": "https://github.com/nicolasmelo1/amy#readme",
|
|
9
|
+
"bugs": "https://github.com/nicolasmelo1/amy/issues",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/nicolasmelo1/amy.git",
|
|
13
|
+
"directory": "plugins/claude"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"registry": "https://registry.npmjs.org"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@amykit/agent-kit": "^0.2.0",
|
|
32
|
+
"@amykit/core": "^0.2.0",
|
|
33
|
+
"@amykit/model-specs": "^0.2.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@amykit/test-fixtures": "^0.1.0"
|
|
37
|
+
}
|
|
38
|
+
}
|