@tangle-network/cli-agent-registry 0.0.0-stage → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -0
- package/README.md +145 -2
- package/dist/backend-types-CJH-wHS3.d.mts +56 -0
- package/dist/backend-types-CREj4iOY.mjs +45 -0
- package/dist/backend-types.d.mts +2 -0
- package/dist/backend-types.mjs +2 -0
- package/dist/cli-auth-bundle.d.mts +49 -0
- package/dist/cli-auth-bundle.mjs +92 -0
- package/dist/cli-auth-placeholder.d.mts +54 -0
- package/dist/cli-auth-placeholder.mjs +200 -0
- package/dist/cli-backend-auth-env.d.mts +6 -0
- package/dist/cli-backend-auth-env.mjs +44 -0
- package/dist/default-backend.d.mts +24 -0
- package/dist/default-backend.mjs +31 -0
- package/dist/harness-DIdVNSwl.mjs +167 -0
- package/dist/index.d.mts +1400 -0
- package/dist/index.mjs +146 -0
- package/dist/local-cli-auth.d.mts +14 -0
- package/dist/local-cli-auth.mjs +31 -0
- package/dist/native-session-contracts-DWFzgoye.mjs +1162 -0
- package/dist/native-session-contracts.d.mts +354 -0
- package/dist/native-session-contracts.mjs +2 -0
- package/dist/servable-models.d.mts +104 -0
- package/dist/servable-models.mjs +147 -0
- package/dist/session-contract-BC75JdnA.d.mts +131 -0
- package/dist/session-contract.d.mts +2 -0
- package/dist/session-contract.mjs +190 -0
- package/dist/specs-BSELpX1h.d.mts +415 -0
- package/dist/specs-Bbiay_8K.mjs +704 -0
- package/dist/specs.d.mts +3 -0
- package/dist/specs.mjs +3 -0
- package/package.json +81 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Copyright (c) 2025 Tangle Network
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. No part of this Software may be reproduced, distributed, or
|
|
7
|
+
transmitted in any form or by any means, including photocopying, recording, or
|
|
8
|
+
other electronic or mechanical methods, without the prior written permission of
|
|
9
|
+
Tangle Network.
|
|
10
|
+
|
|
11
|
+
For licensing inquiries, contact: hello@tangle.tools
|
package/README.md
CHANGED
|
@@ -1,3 +1,146 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tangle-network/cli-agent-registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI harness descriptors, capabilities, model wiring, and local credential discovery.
|
|
4
|
+
Use the narrowest entrypoint for the runtime:
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { getSpec } from "@tangle-network/cli-agent-registry/specs";
|
|
8
|
+
import {
|
|
9
|
+
describeLocalCliAuthExpectation,
|
|
10
|
+
discoverLocalCliAuthFiles,
|
|
11
|
+
} from "@tangle-network/cli-agent-registry/local-cli-auth";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`/specs` and `/backend-types` are browser-safe. `/local-cli-auth` reads the host
|
|
15
|
+
filesystem and is Node-only. Workspace consumers use the same built declarations
|
|
16
|
+
as published consumers; build the registry before checking or bundling a consumer.
|
|
17
|
+
|
|
18
|
+
Credential discovery follows `CODEX_HOME`, `CLAUDE_CONFIG_DIR` and Claude's
|
|
19
|
+
`CLAUDE_SECURESTORAGE_CONFIG_DIR`; secure storage does not move settings files.
|
|
20
|
+
Pass `{ credentialOnly: true }` to read only the native credential file, without
|
|
21
|
+
settings. An explicit `homeDir` isolates discovery from ambient environment
|
|
22
|
+
overrides unless `env` is also supplied. Returned contents are sensitive raw files,
|
|
23
|
+
not proof of a valid OAuth subscription. Callers own consent and native login
|
|
24
|
+
validation before enrollment. This filesystem reader does not read macOS Keychain.
|
|
25
|
+
|
|
26
|
+
## Identity, availability and implementation
|
|
27
|
+
|
|
28
|
+
Interface describes recognized identities, including historical ones. This
|
|
29
|
+
installation declares its backend vocabulary positively; importing it must not
|
|
30
|
+
require every harness subsequently recognized by Interface. `BackendType` derives
|
|
31
|
+
from the local declaration. Accepted IDs are unchanged. Removing an executable
|
|
32
|
+
must not invalidate saved identities.
|
|
33
|
+
|
|
34
|
+
`createAdapterRegistry(selectedSpecs)` owns an independent factory map and snapshots
|
|
35
|
+
its wiring requirements. Each selected executable descriptor requires a factory and
|
|
36
|
+
its native-session contract (or the existing explicit unsupported classification).
|
|
37
|
+
Duplicate descriptors and duplicate factory registration fail; the explicit test
|
|
38
|
+
override remains. Later descriptor mutation cannot weaken those requirements.
|
|
39
|
+
|
|
40
|
+
Existing module-level methods use a default registry over `CLI_AGENT_SPECS`, so
|
|
41
|
+
current consumers retain their requirements. Registration does not authorize execution:
|
|
42
|
+
callers must resolve an admitted descriptor and enforce capabilities. Unregistering a
|
|
43
|
+
selected factory still fails startup; it is not a way to drain live sessions. The
|
|
44
|
+
sidecar's all-provider import composition still needs a selected deployment manifest.
|
|
45
|
+
|
|
46
|
+
## Adapter-owned profile semantics
|
|
47
|
+
|
|
48
|
+
`CliProviderAdapterBase` no longer guesses native profile behavior from a storage
|
|
49
|
+
path or owns Pi's private instruction-directory calculation. A subclass explicitly
|
|
50
|
+
supplies `profileHarness` and, when needed, `profileAgentDirectory`. Claude Code,
|
|
51
|
+
Codex, OpenCode, Kimi, NanoClaw, Pi, Hermes and OpenClaw declare their existing
|
|
52
|
+
profile semantics; Pi resolves its own native private directory.
|
|
53
|
+
|
|
54
|
+
Custom base subclasses using portable profile lowering must declare their identity.
|
|
55
|
+
Ordinary execution without a profile remains available without this declaration.
|
|
56
|
+
Secret concurrent-profile revisions and managed quotes are separate trusted
|
|
57
|
+
implementation capabilities: OpenCode explicitly opts into the existing transports.
|
|
58
|
+
Neither a caller's config key nor the provider name grants them. Native-session,
|
|
59
|
+
private-root, secret-handling and budget checks remain in place.
|
|
60
|
+
|
|
61
|
+
### System laws
|
|
62
|
+
|
|
63
|
+
- `tests/adapter-removal.contract.test.ts`: selected descriptors require factories,
|
|
64
|
+
omitted descriptors do not, registries are isolated, duplicate descriptors fail,
|
|
65
|
+
and mutation cannot change the captured requirements.
|
|
66
|
+
- `../sdk-provider-cli-base/tests/profile-ownership-process.test.ts`: a real Node
|
|
67
|
+
child observes materialized profiles and private instruction placement; unsupported
|
|
68
|
+
profiles fail before spawn; ordinary execution needs no implicit profile support;
|
|
69
|
+
names/config keys do not grant managed quotes. Node is a process fixture, not a
|
|
70
|
+
claimed substitute for a native coding harness.
|
|
71
|
+
- Native conformance is separately bound to pinned CLI versions, declared session
|
|
72
|
+
contracts and adapter/shared-runner source. Stale receipts fail; never edit their
|
|
73
|
+
hashes to manufacture proof.
|
|
74
|
+
|
|
75
|
+
## Executed validation
|
|
76
|
+
|
|
77
|
+
Foundation proof: Actions **38684058864**, artifact **11948704158**, commit
|
|
78
|
+
`64bb4f84358990dbd85d64aaf97eb73985f0a964`. Affected builds/typechecks passed;
|
|
79
|
+
**122 registry tests and 355 runtime-contract tests passed**.
|
|
80
|
+
|
|
81
|
+
Expanded proof: Actions **38687071248**, artifact **11950383351**, commit
|
|
82
|
+
`559f2550daa698ddb648ec6470aec477aff84a71`:
|
|
83
|
+
|
|
84
|
+
- The sidecar dependency closure builds.
|
|
85
|
+
- All provider packages and the registry pass typechecking.
|
|
86
|
+
- All 14 provider suites plus the shared base pass: **1,502 tests**, with 65 skipped.
|
|
87
|
+
- Runtime-contract tests: **355 passed**.
|
|
88
|
+
- Registry: **121 passed, 1 failed**. The failure correctly names four stale native
|
|
89
|
+
receipts after the shared-base change: Claude Code, Codex, OpenCode and Pi.
|
|
90
|
+
|
|
91
|
+
### Native qualification: not passed
|
|
92
|
+
|
|
93
|
+
Two native attempts built the pinned CLI image, then failed connecting to the local
|
|
94
|
+
Sandbox shim before any harness execution. No fresh passing receipt was produced.
|
|
95
|
+
|
|
96
|
+
- Actions **38687071248**, artifact **11950938690**, source
|
|
97
|
+
`559f2550daa698ddb648ec6470aec477aff84a71`: fetch failure, exit 1; owned-container
|
|
98
|
+
cleanup passed.
|
|
99
|
+
- Actions **38688402352**, artifact **11951190503**, source
|
|
100
|
+
`d640034db5ee590c9851daa0049c32d6a3e4c160`: the same attempt with bounded transport
|
|
101
|
+
cause diagnostics reports `UND_ERR_CONNECT_TIMEOUT` for all four requests.
|
|
102
|
+
Exit 1; owned-container cleanup passed. Only test-driver diagnostics changed.
|
|
103
|
+
- Actions **38688973460**, artifact **11951770808**: an isolated network diagnostic
|
|
104
|
+
confirms that the Docker bridge gateway is not a local address in the runner's
|
|
105
|
+
network namespace. The runner's own loopback server answers, but the gateway
|
|
106
|
+
probes do not. No proxy environment is involved. A successful diagnostic job is
|
|
107
|
+
not a successful native qualification.
|
|
108
|
+
|
|
109
|
+
The driver assumes that its host-side shim and Docker bridge share a reachable
|
|
110
|
+
network context. This runner does not satisfy that assumption. Co-locate the test
|
|
111
|
+
services with the Docker network and expose the client endpoint explicitly, or use
|
|
112
|
+
an appropriate co-located runner, before repeating native qualification. Do not
|
|
113
|
+
weaken TLS, network isolation, receipt checks or production controls to force a pass.
|
|
114
|
+
All temporary branch-only proof/diagnostic workflows were removed after these runs.
|
|
115
|
+
No production endpoint or real model was used. Later documentation and workflow
|
|
116
|
+
cleanup commits are not retroactively included in the source commits named above.
|
|
117
|
+
|
|
118
|
+
## Exact remaining work
|
|
119
|
+
|
|
120
|
+
- [ ] Repair the native proof's runner/network topology; obtain genuine passing
|
|
121
|
+
receipts for the changed runner; rerun the registry/invariant gate.
|
|
122
|
+
- [ ] Select sidecar implementation imports, API catalog admission and image/CLI
|
|
123
|
+
dependencies from one explicit deployment manifest. Registry omission is not
|
|
124
|
+
physical package or executable removal.
|
|
125
|
+
- [ ] Supply real start/resume availability to app admission and pickers; preserve
|
|
126
|
+
old IDs/history; do not pre-coerce saved or explicit requested IDs.
|
|
127
|
+
- [ ] Consolidate remaining per-harness profile/MCP/hooks, auth, invocation, session
|
|
128
|
+
and packaging facts with their owners. Replace the app's duplicated profile
|
|
129
|
+
support table. Common-base inference removal is done, not this entire step.
|
|
130
|
+
- [ ] Declare ACP's selected backing dependency, including its current Hermes
|
|
131
|
+
default; reject missing/retired backings before launch while preserving the
|
|
132
|
+
closed command allowlist and credential isolation.
|
|
133
|
+
- [ ] Consolidate agent-runtime's local invocation and native usage readers with
|
|
134
|
+
their owners; keep historical codecs usable without installed executables.
|
|
135
|
+
- [ ] Physically omit each adapter package/CLI in turn and prove remaining startup,
|
|
136
|
+
cancellation/resume, readable history and secret isolation.
|
|
137
|
+
- [ ] Complete sidecar typecheck and real HTTP/UI/packed-consumer coverage for the
|
|
138
|
+
selected catalog; update the maintained owner/roadmap indexes.
|
|
139
|
+
- [ ] Independent review, final-head checks, merge, package publication/cohort
|
|
140
|
+
adoption and authorized served verification through maintained automation.
|
|
141
|
+
|
|
142
|
+
Continue the same PRs: agent-container **#10961**, agent-sdk **#498**, agent-app
|
|
143
|
+
**#1116**. Their documents retain the executed evidence and remaining adoption.
|
|
144
|
+
Agent-runtime and Braid have no implementation PR from this attempt; change Braid
|
|
145
|
+
only where shared-contract adoption requires it. No production harness is retired
|
|
146
|
+
merely because its implementation was refactored.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region ../runtime-contracts/dist/backend-types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* This distribution's backend vocabulary, declared positively. The shared
|
|
6
|
+
* Interface vocabulary also contains historical and differently hosted harnesses;
|
|
7
|
+
* knowing one upstream must not require installing it here or make this module
|
|
8
|
+
* throw at import time. Keep old wire identities readable when retiring execution.
|
|
9
|
+
*
|
|
10
|
+
* Gemini is not a direct Sandbox backend; it is available only through a backing
|
|
11
|
+
* transport. Adding it upstream never implicitly makes it dispatchable here.
|
|
12
|
+
* The spec/adapter composition must still prove every backend it actually enables.
|
|
13
|
+
*/
|
|
14
|
+
declare const BACKEND_TYPE: {
|
|
15
|
+
readonly OPENCODE: "opencode";
|
|
16
|
+
readonly CLAUDE_CODE: "claude-code";
|
|
17
|
+
readonly KIMI_CODE: "kimi-code";
|
|
18
|
+
readonly CODEX: "codex";
|
|
19
|
+
readonly AMP: "amp";
|
|
20
|
+
readonly FACTORY_DROIDS: "factory-droids";
|
|
21
|
+
readonly PI: "pi";
|
|
22
|
+
readonly PRIME: "prime";
|
|
23
|
+
readonly HERMES: "hermes";
|
|
24
|
+
readonly FORGE: "forge";
|
|
25
|
+
readonly OPENCLAW: "openclaw";
|
|
26
|
+
readonly NANOCLAW: "nanoclaw";
|
|
27
|
+
readonly ACP: "acp";
|
|
28
|
+
readonly CURSOR: "cursor";
|
|
29
|
+
readonly CLI_BASE: "cli-base";
|
|
30
|
+
};
|
|
31
|
+
/** Derived from the local declaration, not the complement of the upstream enum. */
|
|
32
|
+
type BackendType = (typeof BACKEND_TYPE)[keyof typeof BACKEND_TYPE];
|
|
33
|
+
/** Stable declaration order; registry tests check the shipped spec composition. */
|
|
34
|
+
declare const ALL_BACKEND_TYPES: [BackendType, ...BackendType[]];
|
|
35
|
+
declare const backendTypeSchema: z.ZodEnum<{
|
|
36
|
+
"claude-code": "claude-code";
|
|
37
|
+
nanoclaw: "nanoclaw";
|
|
38
|
+
codex: "codex";
|
|
39
|
+
opencode: "opencode";
|
|
40
|
+
"kimi-code": "kimi-code";
|
|
41
|
+
pi: "pi";
|
|
42
|
+
prime: "prime";
|
|
43
|
+
hermes: "hermes";
|
|
44
|
+
openclaw: "openclaw";
|
|
45
|
+
amp: "amp";
|
|
46
|
+
"factory-droids": "factory-droids";
|
|
47
|
+
forge: "forge";
|
|
48
|
+
cursor: "cursor";
|
|
49
|
+
acp: "acp";
|
|
50
|
+
"cli-base": "cli-base";
|
|
51
|
+
}>;
|
|
52
|
+
declare function isBackendType(value: string): value is BackendType;
|
|
53
|
+
declare function parseBackendType(value: string): BackendType;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { isBackendType as a, backendTypeSchema as i, BACKEND_TYPE as n, parseBackendType as o, BackendType as r, ALL_BACKEND_TYPES as t };
|
|
56
|
+
//# sourceMappingURL=backend-types-CJH-wHS3.d.mts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region ../runtime-contracts/dist/backend-types.js
|
|
3
|
+
/**
|
|
4
|
+
* This distribution's backend vocabulary, declared positively. The shared
|
|
5
|
+
* Interface vocabulary also contains historical and differently hosted harnesses;
|
|
6
|
+
* knowing one upstream must not require installing it here or make this module
|
|
7
|
+
* throw at import time. Keep old wire identities readable when retiring execution.
|
|
8
|
+
*
|
|
9
|
+
* Gemini is not a direct Sandbox backend; it is available only through a backing
|
|
10
|
+
* transport. Adding it upstream never implicitly makes it dispatchable here.
|
|
11
|
+
* The spec/adapter composition must still prove every backend it actually enables.
|
|
12
|
+
*/
|
|
13
|
+
const BACKEND_TYPE = {
|
|
14
|
+
OPENCODE: "opencode",
|
|
15
|
+
CLAUDE_CODE: "claude-code",
|
|
16
|
+
KIMI_CODE: "kimi-code",
|
|
17
|
+
CODEX: "codex",
|
|
18
|
+
AMP: "amp",
|
|
19
|
+
FACTORY_DROIDS: "factory-droids",
|
|
20
|
+
PI: "pi",
|
|
21
|
+
PRIME: "prime",
|
|
22
|
+
HERMES: "hermes",
|
|
23
|
+
FORGE: "forge",
|
|
24
|
+
OPENCLAW: "openclaw",
|
|
25
|
+
NANOCLAW: "nanoclaw",
|
|
26
|
+
ACP: "acp",
|
|
27
|
+
CURSOR: "cursor",
|
|
28
|
+
CLI_BASE: "cli-base"
|
|
29
|
+
};
|
|
30
|
+
/** Stable declaration order; registry tests check the shipped spec composition. */
|
|
31
|
+
const ALL_BACKEND_TYPES = Object.values(BACKEND_TYPE);
|
|
32
|
+
const backendTypeSchema = z.enum(ALL_BACKEND_TYPES);
|
|
33
|
+
z.string().regex(/^[A-Za-z0-9_.:@-]{1,256}$/);
|
|
34
|
+
const BACKEND_TYPE_SET = new Set(ALL_BACKEND_TYPES);
|
|
35
|
+
function isBackendType(value) {
|
|
36
|
+
return BACKEND_TYPE_SET.has(value);
|
|
37
|
+
}
|
|
38
|
+
function parseBackendType(value) {
|
|
39
|
+
if (!isBackendType(value)) throw new Error(`Unknown backend type "${value}". Expected one of: ${ALL_BACKEND_TYPES.join(", ")}`);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { parseBackendType as a, isBackendType as i, BACKEND_TYPE as n, backendTypeSchema as r, ALL_BACKEND_TYPES as t };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=backend-types-CREj4iOY.mjs.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as isBackendType, i as backendTypeSchema, n as BACKEND_TYPE, o as parseBackendType, r as BackendType, t as ALL_BACKEND_TYPES } from "./backend-types-CJH-wHS3.mjs";
|
|
2
|
+
export { ALL_BACKEND_TYPES, BACKEND_TYPE, BackendType, backendTypeSchema, isBackendType, parseBackendType };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/cli-auth-bundle.d.ts
|
|
4
|
+
type LocalCliAuthFile = {
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
mode?: number;
|
|
8
|
+
};
|
|
9
|
+
declare const AUTH_BUNDLE_PATHS: Readonly<{
|
|
10
|
+
readonly codex: ".codex/auth.json";
|
|
11
|
+
readonly "claude-code": ".claude/.credentials.json";
|
|
12
|
+
}>;
|
|
13
|
+
/** Convert a native credential JSON bundle without reading host credentials. */
|
|
14
|
+
declare function materializeCliAuthBundle(backendType: string, rawBundle: string): LocalCliAuthFile[];
|
|
15
|
+
declare const CLI_AUTH_REFERENCE_ENV_VAR = "AGENT_INTERNAL_CLI_AUTH_REF";
|
|
16
|
+
declare const cliAuthReferenceSchema: z.ZodObject<{
|
|
17
|
+
account: z.ZodString;
|
|
18
|
+
secretEnv: z.ZodString;
|
|
19
|
+
format: z.ZodEnum<{
|
|
20
|
+
token: "token";
|
|
21
|
+
bundle: "bundle";
|
|
22
|
+
files: "files";
|
|
23
|
+
}>;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
/** A supplied account binding label, not a verified native account principal. */
|
|
26
|
+
type CliAuthReference = z.infer<typeof cliAuthReferenceSchema>;
|
|
27
|
+
type CliAuthMaterial = {
|
|
28
|
+
reference: CliAuthReference;
|
|
29
|
+
authFiles: LocalCliAuthFile[];
|
|
30
|
+
tokenEnvironment: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
declare const cliAuthFilesSchema: z.ZodArray<z.ZodObject<{
|
|
33
|
+
path: z.ZodString;
|
|
34
|
+
content: z.ZodString;
|
|
35
|
+
mode: z.ZodOptional<z.ZodNumber>;
|
|
36
|
+
}, z.core.$strict>>;
|
|
37
|
+
declare function assertCliAuthReferenceSupported(backendType: string, model: {
|
|
38
|
+
cliAuth?: CliAuthReference;
|
|
39
|
+
authMode?: string;
|
|
40
|
+
apiKey?: string;
|
|
41
|
+
apiKeyEnv?: string;
|
|
42
|
+
baseUrl?: string;
|
|
43
|
+
authFiles?: LocalCliAuthFile[];
|
|
44
|
+
}): CliAuthReference | undefined;
|
|
45
|
+
/** Resolve only the selected injected secret, without copying it into a public config. */
|
|
46
|
+
declare function resolveCliAuthReference(backendType: string, model: Parameters<typeof assertCliAuthReferenceSupported>[1], environment: Readonly<Record<string, string | undefined>>): CliAuthMaterial | undefined;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { AUTH_BUNDLE_PATHS, CLI_AUTH_REFERENCE_ENV_VAR, CliAuthMaterial, CliAuthReference, LocalCliAuthFile, assertCliAuthReferenceSupported, cliAuthFilesSchema, cliAuthReferenceSchema, materializeCliAuthBundle, resolveCliAuthReference };
|
|
49
|
+
//# sourceMappingURL=cli-auth-bundle.d.mts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { l as getSpec } from "./specs-Bbiay_8K.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/cli-auth-bundle.ts
|
|
4
|
+
const DEFAULT_MODE = 384;
|
|
5
|
+
const AUTH_BUNDLE_PATHS = Object.freeze({
|
|
6
|
+
codex: ".codex/auth.json",
|
|
7
|
+
"claude-code": ".claude/.credentials.json"
|
|
8
|
+
});
|
|
9
|
+
/** Convert a native credential JSON bundle without reading host credentials. */
|
|
10
|
+
function materializeCliAuthBundle(backendType, rawBundle) {
|
|
11
|
+
if (backendType !== "codex" && backendType !== "claude-code") throw new Error("CLI auth bundle is not supported for the selected backend");
|
|
12
|
+
if (typeof rawBundle !== "string") throw new Error("CLI auth bundle must be a JSON object");
|
|
13
|
+
let value;
|
|
14
|
+
try {
|
|
15
|
+
value = JSON.parse(rawBundle);
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error("CLI auth bundle must be a JSON object");
|
|
18
|
+
}
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("CLI auth bundle must be a JSON object");
|
|
20
|
+
return [{
|
|
21
|
+
path: AUTH_BUNDLE_PATHS[backendType],
|
|
22
|
+
content: rawBundle,
|
|
23
|
+
mode: DEFAULT_MODE
|
|
24
|
+
}];
|
|
25
|
+
}
|
|
26
|
+
const CLI_AUTH_REFERENCE_ENV_VAR = "AGENT_INTERNAL_CLI_AUTH_REF";
|
|
27
|
+
const cliAuthReferenceSchema = z.object({
|
|
28
|
+
account: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9@._+-]{0,127}$/),
|
|
29
|
+
secretEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}$/),
|
|
30
|
+
format: z.enum([
|
|
31
|
+
"token",
|
|
32
|
+
"bundle",
|
|
33
|
+
"files"
|
|
34
|
+
])
|
|
35
|
+
}).strict();
|
|
36
|
+
const cliAuthFilesSchema = z.array(z.object({
|
|
37
|
+
path: z.string().min(1).max(4096).refine((value) => !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && !/[\\\0]/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..")),
|
|
38
|
+
content: z.string().max(1024 * 1024),
|
|
39
|
+
mode: z.number().int().min(0).max(448).refine((mode) => (mode & 63) === 0).optional()
|
|
40
|
+
}).strict()).min(1).max(128);
|
|
41
|
+
function assertCliAuthReferenceSupported(backendType, model) {
|
|
42
|
+
if (model.cliAuth === void 0) return void 0;
|
|
43
|
+
const parsedReference = cliAuthReferenceSchema.safeParse(model.cliAuth);
|
|
44
|
+
if (!parsedReference.success) throw new Error("Invalid native authentication reference");
|
|
45
|
+
const reference = parsedReference.data;
|
|
46
|
+
if (model.authMode === "api-key" || model.apiKey !== void 0 || model.apiKeyEnv !== void 0 || model.baseUrl !== void 0 || model.authFiles !== void 0) throw new Error("cliAuth cannot be combined with API credentials, baseUrl, or inline authFiles");
|
|
47
|
+
const spec = getSpec(backendType);
|
|
48
|
+
if (!spec?.hasCliAuth) throw new Error(`Native CLI authentication is unsupported by ${backendType}`);
|
|
49
|
+
if (reference.format === "token" && !spec.envVars.oauthToken) throw new Error(`Native subscription tokens are unsupported by ${backendType}`);
|
|
50
|
+
if (reference.format === "bundle" && backendType !== "codex" && backendType !== "claude-code") throw new Error(`Native credential bundles are unsupported by ${backendType}`);
|
|
51
|
+
return reference;
|
|
52
|
+
}
|
|
53
|
+
/** Resolve only the selected injected secret, without copying it into a public config. */
|
|
54
|
+
function resolveCliAuthReference(backendType, model, environment) {
|
|
55
|
+
const reference = assertCliAuthReferenceSupported(backendType, model);
|
|
56
|
+
if (!reference) return void 0;
|
|
57
|
+
const value = environment[reference.secretEnv];
|
|
58
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Missing native credential environment variable: ${reference.secretEnv}`);
|
|
59
|
+
if (reference.format === "token") {
|
|
60
|
+
const tokenEnv = getSpec(backendType)?.envVars.oauthToken;
|
|
61
|
+
if (!tokenEnv) throw new Error(`Native subscription tokens are unsupported by ${backendType}`);
|
|
62
|
+
if (/\s/.test(value) || value.startsWith("{") || value.startsWith("[")) throw new Error("Native token authentication requires a raw token, not a credential wrapper");
|
|
63
|
+
return {
|
|
64
|
+
reference,
|
|
65
|
+
authFiles: [],
|
|
66
|
+
tokenEnvironment: { [tokenEnv]: value }
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (reference.format === "bundle") return {
|
|
70
|
+
reference,
|
|
71
|
+
authFiles: materializeCliAuthBundle(backendType, value),
|
|
72
|
+
tokenEnvironment: {}
|
|
73
|
+
};
|
|
74
|
+
let parsed;
|
|
75
|
+
try {
|
|
76
|
+
parsed = JSON.parse(value);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new Error("Native authFiles secret must contain a JSON array of auth files");
|
|
79
|
+
}
|
|
80
|
+
const files = cliAuthFilesSchema.safeParse(parsed);
|
|
81
|
+
if (!files.success) throw new Error("Native authFiles secret must contain a JSON array of auth files");
|
|
82
|
+
if (backendType === "codex" && (files.data.length !== 1 || files.data[0]?.path !== AUTH_BUNDLE_PATHS.codex || files.data[0]?.content.trim().length === 0)) throw new Error("Codex native auth files require only .codex/auth.json");
|
|
83
|
+
return {
|
|
84
|
+
reference,
|
|
85
|
+
authFiles: files.data,
|
|
86
|
+
tokenEnvironment: {}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
export { AUTH_BUNDLE_PATHS, CLI_AUTH_REFERENCE_ENV_VAR, assertCliAuthReferenceSupported, cliAuthFilesSchema, cliAuthReferenceSchema, materializeCliAuthBundle, resolveCliAuthReference };
|
|
91
|
+
|
|
92
|
+
//# sourceMappingURL=cli-auth-bundle.mjs.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { CliAuthMaterial, CliAuthReference } from "./cli-auth-bundle.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/cli-auth-placeholder.d.ts
|
|
5
|
+
declare const metadataSchema: z.ZodObject<{
|
|
6
|
+
accountId: z.ZodOptional<z.ZodString>;
|
|
7
|
+
planType: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
8
|
+
free: "free";
|
|
9
|
+
plus: "plus";
|
|
10
|
+
pro: "pro";
|
|
11
|
+
business: "business";
|
|
12
|
+
enterprise: "enterprise";
|
|
13
|
+
edu: "edu";
|
|
14
|
+
team: "team";
|
|
15
|
+
}>, z.ZodEnum<{
|
|
16
|
+
free: "free";
|
|
17
|
+
pro: "pro";
|
|
18
|
+
enterprise: "enterprise";
|
|
19
|
+
team: "team";
|
|
20
|
+
max: "max";
|
|
21
|
+
}>]>>;
|
|
22
|
+
accessTokenExpiresAt: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
hasRefreshToken: z.ZodBoolean;
|
|
24
|
+
}, z.core.$strict>;
|
|
25
|
+
/** Routing/display hints only; these fields do not verify an account principal. */
|
|
26
|
+
type CliAuthPlaceholderMetadata = z.infer<typeof metadataSchema>;
|
|
27
|
+
type PlaceholderInput = {
|
|
28
|
+
backendType: string;
|
|
29
|
+
reference: CliAuthReference; /** The existing egress owner supplies this sandbox's declared-slot token. */
|
|
30
|
+
placeholder: string; /** CLI compatibility timestamp, never an assertion of provider validity. */
|
|
31
|
+
now: Date;
|
|
32
|
+
metadata: CliAuthPlaceholderMetadata;
|
|
33
|
+
};
|
|
34
|
+
/** Build sandbox-safe native material without accepting any provider token. */
|
|
35
|
+
declare function buildCliAuthPlaceholderMaterial(input: PlaceholderInput): CliAuthMaterial;
|
|
36
|
+
/**
|
|
37
|
+
* TRUSTED OWNER ONLY. The explicit proxyAccessToken result must reach only the
|
|
38
|
+
* host's existing egress holder. Only nativeMaterial may enter the sandbox.
|
|
39
|
+
* The uploaded bundle, ID token and refresh token are never returned.
|
|
40
|
+
*/
|
|
41
|
+
declare function resolveCliAuthPlaceholder(input: {
|
|
42
|
+
backendType: string;
|
|
43
|
+
reference: CliAuthReference;
|
|
44
|
+
secretValue: string;
|
|
45
|
+
placeholder: string;
|
|
46
|
+
now: Date;
|
|
47
|
+
}): {
|
|
48
|
+
proxyAccessToken: string;
|
|
49
|
+
nativeMaterial: CliAuthMaterial;
|
|
50
|
+
metadata: CliAuthPlaceholderMetadata;
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
export { CliAuthPlaceholderMetadata, buildCliAuthPlaceholderMaterial, resolveCliAuthPlaceholder };
|
|
54
|
+
//# sourceMappingURL=cli-auth-placeholder.d.mts.map
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { AUTH_BUNDLE_PATHS, assertCliAuthReferenceSupported, resolveCliAuthReference } from "./cli-auth-bundle.mjs";
|
|
2
|
+
import { i as baseProxyToken, r as declaredSecretSlotTokens } from "./harness-DIdVNSwl.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/cli-auth-placeholder.ts
|
|
5
|
+
const identity = z.string().regex(/^[A-Za-z0-9_-]{1,256}$/);
|
|
6
|
+
const token = z.string().min(8).max(64 * 1024).regex(/^[^\s\0]+$/);
|
|
7
|
+
const expiry = z.number().int().positive().max(864e13);
|
|
8
|
+
const codexPlan = z.enum([
|
|
9
|
+
"free",
|
|
10
|
+
"plus",
|
|
11
|
+
"pro",
|
|
12
|
+
"business",
|
|
13
|
+
"enterprise",
|
|
14
|
+
"edu",
|
|
15
|
+
"team"
|
|
16
|
+
]);
|
|
17
|
+
const claudePlan = z.enum([
|
|
18
|
+
"free",
|
|
19
|
+
"pro",
|
|
20
|
+
"max",
|
|
21
|
+
"team",
|
|
22
|
+
"enterprise"
|
|
23
|
+
]);
|
|
24
|
+
const metadataSchema = z.object({
|
|
25
|
+
accountId: identity.optional(),
|
|
26
|
+
planType: z.union([codexPlan, claudePlan]).optional(),
|
|
27
|
+
accessTokenExpiresAt: expiry.optional(),
|
|
28
|
+
hasRefreshToken: z.boolean()
|
|
29
|
+
}).strict();
|
|
30
|
+
const codexBundle = z.object({ tokens: z.object({
|
|
31
|
+
access_token: token,
|
|
32
|
+
refresh_token: z.union([token, z.literal("")]).optional(),
|
|
33
|
+
id_token: token,
|
|
34
|
+
account_id: identity.nullable().optional()
|
|
35
|
+
}) });
|
|
36
|
+
const codexClaims = z.object({ "https://api.openai.com/auth": z.object({
|
|
37
|
+
chatgpt_account_id: identity.optional(),
|
|
38
|
+
chatgpt_plan_type: codexPlan.optional(),
|
|
39
|
+
chatgpt_account_is_fedramp: z.boolean().optional()
|
|
40
|
+
}).optional() });
|
|
41
|
+
const claudeBundle = z.object({ claudeAiOauth: z.object({
|
|
42
|
+
accessToken: token,
|
|
43
|
+
refreshToken: token.nullable().optional(),
|
|
44
|
+
expiresAt: expiry.nullable().optional(),
|
|
45
|
+
subscriptionType: claudePlan.nullable().optional()
|
|
46
|
+
}) });
|
|
47
|
+
function invalidMaterial() {
|
|
48
|
+
throw new Error("Unsupported or invalid native credential material");
|
|
49
|
+
}
|
|
50
|
+
function parseJson(raw) {
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
} catch {
|
|
54
|
+
return invalidMaterial();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function jwtClaims(raw) {
|
|
58
|
+
const parts = raw.split(".");
|
|
59
|
+
if (parts.length !== 3 || parts.some((part) => !part)) return invalidMaterial();
|
|
60
|
+
try {
|
|
61
|
+
const payload = (parts[1] ?? "").replaceAll("-", "+").replaceAll("_", "/");
|
|
62
|
+
const bytes = Uint8Array.from(atob(payload), (value) => value.charCodeAt(0));
|
|
63
|
+
return parseJson(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
64
|
+
} catch {
|
|
65
|
+
return invalidMaterial();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function accessTokenExpiry(value) {
|
|
69
|
+
if (value.split(".").length !== 3) return void 0;
|
|
70
|
+
const parsed = z.object({ exp: z.number().int().positive().optional() }).safeParse(jwtClaims(value));
|
|
71
|
+
if (!parsed.success) return invalidMaterial();
|
|
72
|
+
if (parsed.data.exp === void 0) return void 0;
|
|
73
|
+
const result = expiry.safeParse(parsed.data.exp * 1e3);
|
|
74
|
+
if (!result.success) return invalidMaterial();
|
|
75
|
+
return result.data;
|
|
76
|
+
}
|
|
77
|
+
/** Build sandbox-safe native material without accepting any provider token. */
|
|
78
|
+
function buildCliAuthPlaceholderMaterial(input) {
|
|
79
|
+
const reference = assertCliAuthReferenceSupported(input.backendType, { cliAuth: input.reference });
|
|
80
|
+
const metadata = metadataSchema.safeParse(input.metadata);
|
|
81
|
+
const declaredTokens = Object.values(declaredSecretSlotTokens());
|
|
82
|
+
if (!reference || !metadata.success || !Number.isFinite(input.now.getTime()) || !declaredTokens.includes(baseProxyToken(input.placeholder)) || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(input.placeholder)) return invalidMaterial();
|
|
83
|
+
if (input.backendType !== "codex" && input.backendType !== "claude-code") return invalidMaterial();
|
|
84
|
+
if (input.backendType === "codex" && reference.format === "token") return invalidMaterial();
|
|
85
|
+
const { accountId, planType, accessTokenExpiresAt, hasRefreshToken } = metadata.data;
|
|
86
|
+
let content;
|
|
87
|
+
if (input.backendType === "codex") {
|
|
88
|
+
if (planType !== void 0 && !codexPlan.safeParse(planType).success) return invalidMaterial();
|
|
89
|
+
const claims = {
|
|
90
|
+
sub: input.placeholder,
|
|
91
|
+
aud: "tangle-sandbox",
|
|
92
|
+
exp: Math.floor(input.now.getTime() / 1e3)
|
|
93
|
+
};
|
|
94
|
+
content = {
|
|
95
|
+
auth_mode: "chatgpt",
|
|
96
|
+
OPENAI_API_KEY: null,
|
|
97
|
+
tokens: {
|
|
98
|
+
id_token: `e30.${btoa(JSON.stringify(claims)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "")}.tangle-placeholder`,
|
|
99
|
+
access_token: input.placeholder,
|
|
100
|
+
refresh_token: hasRefreshToken ? input.placeholder : "",
|
|
101
|
+
...accountId ? { account_id: accountId } : {}
|
|
102
|
+
},
|
|
103
|
+
last_refresh: input.now.toISOString()
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
if (planType !== void 0 && !claudePlan.safeParse(planType).success) return invalidMaterial();
|
|
107
|
+
content = { claudeAiOauth: {
|
|
108
|
+
accessToken: input.placeholder,
|
|
109
|
+
refreshToken: hasRefreshToken ? input.placeholder : null,
|
|
110
|
+
expiresAt: accessTokenExpiresAt ?? null,
|
|
111
|
+
scopes: ["user:inference"],
|
|
112
|
+
subscriptionType: planType ?? null,
|
|
113
|
+
rateLimitTier: null
|
|
114
|
+
} };
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
reference,
|
|
118
|
+
authFiles: [{
|
|
119
|
+
path: AUTH_BUNDLE_PATHS[input.backendType],
|
|
120
|
+
content: JSON.stringify(content),
|
|
121
|
+
mode: 384
|
|
122
|
+
}],
|
|
123
|
+
tokenEnvironment: {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* TRUSTED OWNER ONLY. The explicit proxyAccessToken result must reach only the
|
|
128
|
+
* host's existing egress holder. Only nativeMaterial may enter the sandbox.
|
|
129
|
+
* The uploaded bundle, ID token and refresh token are never returned.
|
|
130
|
+
*/
|
|
131
|
+
function resolveCliAuthPlaceholder(input) {
|
|
132
|
+
if (input.secretValue.length > 1024 * 1024) return invalidMaterial();
|
|
133
|
+
const material = resolveCliAuthReference(input.backendType, { cliAuth: input.reference }, { [input.reference.secretEnv]: input.secretValue });
|
|
134
|
+
if (!material) return invalidMaterial();
|
|
135
|
+
let proxyAccessToken;
|
|
136
|
+
let metadata;
|
|
137
|
+
let credentials;
|
|
138
|
+
if (input.backendType === "claude-code" && material.reference.format === "token") {
|
|
139
|
+
const parsed = token.safeParse(Object.values(material.tokenEnvironment)[0]);
|
|
140
|
+
if (!parsed.success) return invalidMaterial();
|
|
141
|
+
proxyAccessToken = parsed.data;
|
|
142
|
+
credentials = [proxyAccessToken];
|
|
143
|
+
metadata = { hasRefreshToken: false };
|
|
144
|
+
} else {
|
|
145
|
+
const expectedPath = input.backendType === "codex" ? AUTH_BUNDLE_PATHS.codex : AUTH_BUNDLE_PATHS["claude-code"];
|
|
146
|
+
if (material.authFiles.length !== 1 || material.authFiles[0]?.path !== expectedPath) return invalidMaterial();
|
|
147
|
+
const bundle = parseJson(material.authFiles[0].content);
|
|
148
|
+
if (input.backendType === "codex") {
|
|
149
|
+
const parsed = codexBundle.safeParse(bundle);
|
|
150
|
+
if (!parsed.success) return invalidMaterial();
|
|
151
|
+
const tokens = parsed.data.tokens;
|
|
152
|
+
const parsedClaims = codexClaims.safeParse(jwtClaims(tokens.id_token));
|
|
153
|
+
if (!parsedClaims.success) return invalidMaterial();
|
|
154
|
+
const claims = parsedClaims.data["https://api.openai.com/auth"];
|
|
155
|
+
if (claims?.chatgpt_account_is_fedramp) return invalidMaterial();
|
|
156
|
+
if (tokens.account_id && claims?.chatgpt_account_id && tokens.account_id !== claims.chatgpt_account_id) return invalidMaterial();
|
|
157
|
+
proxyAccessToken = tokens.access_token;
|
|
158
|
+
credentials = [
|
|
159
|
+
proxyAccessToken,
|
|
160
|
+
tokens.id_token,
|
|
161
|
+
tokens.refresh_token ?? ""
|
|
162
|
+
];
|
|
163
|
+
metadata = {
|
|
164
|
+
accountId: tokens.account_id ?? claims?.chatgpt_account_id,
|
|
165
|
+
planType: claims?.chatgpt_plan_type,
|
|
166
|
+
accessTokenExpiresAt: accessTokenExpiry(proxyAccessToken),
|
|
167
|
+
hasRefreshToken: Boolean(tokens.refresh_token)
|
|
168
|
+
};
|
|
169
|
+
} else if (input.backendType === "claude-code") {
|
|
170
|
+
const parsed = claudeBundle.safeParse(bundle);
|
|
171
|
+
if (!parsed.success) return invalidMaterial();
|
|
172
|
+
const oauth = parsed.data.claudeAiOauth;
|
|
173
|
+
proxyAccessToken = oauth.accessToken;
|
|
174
|
+
credentials = [proxyAccessToken, oauth.refreshToken ?? ""];
|
|
175
|
+
metadata = {
|
|
176
|
+
planType: oauth.subscriptionType ?? void 0,
|
|
177
|
+
accessTokenExpiresAt: oauth.expiresAt ?? void 0,
|
|
178
|
+
hasRefreshToken: Boolean(oauth.refreshToken)
|
|
179
|
+
};
|
|
180
|
+
} else return invalidMaterial();
|
|
181
|
+
}
|
|
182
|
+
const nativeMaterial = buildCliAuthPlaceholderMaterial({
|
|
183
|
+
...input,
|
|
184
|
+
metadata
|
|
185
|
+
});
|
|
186
|
+
const publicOutput = JSON.stringify({
|
|
187
|
+
nativeMaterial,
|
|
188
|
+
metadata
|
|
189
|
+
});
|
|
190
|
+
if (credentials.some((value) => value && publicOutput.includes(value))) return invalidMaterial();
|
|
191
|
+
return {
|
|
192
|
+
proxyAccessToken,
|
|
193
|
+
nativeMaterial,
|
|
194
|
+
metadata
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { buildCliAuthPlaceholderMaterial, resolveCliAuthPlaceholder };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=cli-auth-placeholder.mjs.map
|