llm-cli-gateway 2.7.0 → 2.9.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/CHANGELOG.md +82 -0
- package/README.md +28 -1
- package/dist/acp/client.d.ts +78 -0
- package/dist/acp/client.js +201 -0
- package/dist/acp/errors.d.ts +63 -0
- package/dist/acp/errors.js +139 -0
- package/dist/acp/json-rpc-stdio.d.ts +71 -0
- package/dist/acp/json-rpc-stdio.js +375 -0
- package/dist/acp/process-manager.d.ts +66 -0
- package/dist/acp/process-manager.js +364 -0
- package/dist/acp/provider-registry.d.ts +24 -0
- package/dist/acp/provider-registry.js +82 -0
- package/dist/acp/types.d.ts +557 -0
- package/dist/acp/types.js +335 -0
- package/dist/approval-manager.d.ts +1 -0
- package/dist/approval-manager.js +14 -1
- package/dist/async-job-manager.d.ts +3 -0
- package/dist/async-job-manager.js +56 -16
- package/dist/auth.d.ts +4 -0
- package/dist/auth.js +16 -0
- package/dist/cache-stats.d.ts +1 -0
- package/dist/cache-stats.js +19 -11
- package/dist/cli-updater.js +5 -2
- package/dist/codex-json-parser.d.ts +3 -0
- package/dist/codex-json-parser.js +17 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +140 -0
- package/dist/flight-recorder.d.ts +7 -1
- package/dist/flight-recorder.js +33 -6
- package/dist/http-transport.js +21 -18
- package/dist/index.js +104 -34
- package/dist/job-store.d.ts +4 -0
- package/dist/job-store.js +16 -4
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.js +90 -8
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +67 -2
- package/dist/provider-tool-capabilities.d.ts +38 -0
- package/dist/provider-tool-capabilities.js +142 -0
- package/dist/request-context.d.ts +4 -0
- package/dist/request-context.js +16 -0
- package/dist/request-helpers.d.ts +4 -4
- package/dist/request-limits.d.ts +8 -0
- package/dist/request-limits.js +49 -0
- package/dist/secret-redaction.d.ts +3 -0
- package/dist/secret-redaction.js +53 -0
- package/dist/session-manager-pg.js +8 -5
- package/dist/session-manager.d.ts +1 -0
- package/dist/session-manager.js +2 -0
- package/dist/upstream-contracts.d.ts +27 -0
- package/dist/upstream-contracts.js +131 -0
- package/migrations/004_session_owner_principal.sql +10 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -46,6 +46,23 @@ export interface ProviderFeatureCapability {
|
|
|
46
46
|
details?: string;
|
|
47
47
|
values?: string[];
|
|
48
48
|
}
|
|
49
|
+
export type ProviderAcpStatus = "native_smoke_passed" | "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist" | "not_applicable";
|
|
50
|
+
export type ProviderAcpMediation = "native" | "adapter_mediated" | "none";
|
|
51
|
+
export type ProviderAcpSmokeStatus = "passed" | "not_run" | "unsupported";
|
|
52
|
+
export interface ProviderAcpCapability {
|
|
53
|
+
status: ProviderAcpStatus;
|
|
54
|
+
mediation: ProviderAcpMediation;
|
|
55
|
+
targetVersion: string;
|
|
56
|
+
entrypoint: {
|
|
57
|
+
command: string;
|
|
58
|
+
args: string[];
|
|
59
|
+
} | null;
|
|
60
|
+
runtimeEnabled: boolean;
|
|
61
|
+
smokeSupported: boolean;
|
|
62
|
+
smokeStatus: ProviderAcpSmokeStatus;
|
|
63
|
+
caveats: string[];
|
|
64
|
+
docs: string;
|
|
65
|
+
}
|
|
49
66
|
export type ProviderFeatureMap = Record<string, ProviderFeatureCapability>;
|
|
50
67
|
export interface ProviderCapabilityControls {
|
|
51
68
|
allowlist: ProviderToolControl;
|
|
@@ -54,6 +71,25 @@ export interface ProviderCapabilityControls {
|
|
|
54
71
|
nativeSkills: ProviderToolControl;
|
|
55
72
|
[name: string]: ProviderToolControl;
|
|
56
73
|
}
|
|
74
|
+
export type AcpFrozenClassification = "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
75
|
+
export interface AcpProviderContract {
|
|
76
|
+
classification: AcpFrozenClassification;
|
|
77
|
+
summary: string;
|
|
78
|
+
}
|
|
79
|
+
export interface AcpContractMetadata {
|
|
80
|
+
protocol: "Agent Client Protocol";
|
|
81
|
+
outOfScope: "Agent Communication Protocol";
|
|
82
|
+
mcpFrontendRemains: true;
|
|
83
|
+
acpIsInternalProviderTransport: true;
|
|
84
|
+
defaultTransport: "cli";
|
|
85
|
+
hostServicesDenyByDefault: true;
|
|
86
|
+
noRawAcpJsonRpcTool: true;
|
|
87
|
+
adapterSupportIsNotNative: true;
|
|
88
|
+
contractDoc: "docs/acp-contract.md";
|
|
89
|
+
nonGoals: readonly string[];
|
|
90
|
+
providers: Readonly<Record<ProviderCapabilityId, AcpProviderContract>>;
|
|
91
|
+
}
|
|
92
|
+
export declare const ACP_CONTRACT: AcpContractMetadata;
|
|
57
93
|
export interface ProviderToolCapabilities {
|
|
58
94
|
schemaVersion: "provider-tool-capabilities.v2";
|
|
59
95
|
generatedAt: string;
|
|
@@ -62,6 +98,8 @@ export interface ProviderToolCapabilities {
|
|
|
62
98
|
gatewayRequestTools: string[];
|
|
63
99
|
modelInfo: CliInfo | GrokApiModelInfo;
|
|
64
100
|
summary: string;
|
|
101
|
+
acpContract: AcpProviderContract;
|
|
102
|
+
acp: ProviderAcpCapability;
|
|
65
103
|
controls: ProviderCapabilityControls;
|
|
66
104
|
features: ProviderFeatureMap;
|
|
67
105
|
discoveredSkills: ProviderSkillCapability[];
|
|
@@ -11,6 +11,146 @@ const MAX_SKILL_BYTES = 64 * 1024;
|
|
|
11
11
|
const MAX_CONFIG_BYTES = 128 * 1024;
|
|
12
12
|
const MAX_PROVIDER_TOOLS_PER_SKILL = 50;
|
|
13
13
|
const CAPABILITY_CACHE_TTL_MS = 60 * 1000;
|
|
14
|
+
export const ACP_CONTRACT = {
|
|
15
|
+
protocol: "Agent Client Protocol",
|
|
16
|
+
outOfScope: "Agent Communication Protocol",
|
|
17
|
+
mcpFrontendRemains: true,
|
|
18
|
+
acpIsInternalProviderTransport: true,
|
|
19
|
+
defaultTransport: "cli",
|
|
20
|
+
hostServicesDenyByDefault: true,
|
|
21
|
+
noRawAcpJsonRpcTool: true,
|
|
22
|
+
adapterSupportIsNotNative: true,
|
|
23
|
+
contractDoc: "docs/acp-contract.md",
|
|
24
|
+
nonGoals: [
|
|
25
|
+
"Replace the MCP server.",
|
|
26
|
+
"Ship an outbound ACP server or frontend in this slice.",
|
|
27
|
+
"Wrap every provider immediately.",
|
|
28
|
+
"Run adapter-mediated providers by default.",
|
|
29
|
+
"Grant write or terminal HostServices by default.",
|
|
30
|
+
"Expose raw ACP JSON-RPC to agents.",
|
|
31
|
+
"Implement any agent-to-agent Agent Communication Protocol layer.",
|
|
32
|
+
],
|
|
33
|
+
providers: {
|
|
34
|
+
mistral: {
|
|
35
|
+
classification: "native_candidate",
|
|
36
|
+
summary: "Mistral Vibe exposes native ACP via vibe-acp; first runtime pilot candidate.",
|
|
37
|
+
},
|
|
38
|
+
grok: {
|
|
39
|
+
classification: "native_candidate",
|
|
40
|
+
summary: "xAI Grok CLI exposes native ACP via grok agent stdio; second runtime pilot candidate.",
|
|
41
|
+
},
|
|
42
|
+
codex: {
|
|
43
|
+
classification: "adapter_mediated_deferred",
|
|
44
|
+
summary: "OpenAI Codex CLI has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
|
|
45
|
+
},
|
|
46
|
+
claude: {
|
|
47
|
+
classification: "adapter_mediated_deferred",
|
|
48
|
+
summary: "Anthropic Claude Code has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
|
|
49
|
+
},
|
|
50
|
+
gemini: {
|
|
51
|
+
classification: "absent_watchlist",
|
|
52
|
+
summary: "Google Antigravity agy 1.0.7 has no ACP surface; watchlist item only.",
|
|
53
|
+
},
|
|
54
|
+
grok_api: {
|
|
55
|
+
classification: "absent_watchlist",
|
|
56
|
+
summary: "Grok API is an HTTP provider with no ACP process transport; watchlist item only.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const ACP_DOCS_REFERENCE = "docs/plans/first-class-acp-gateway-extension.dag.toml";
|
|
61
|
+
const ACP_CAPABILITIES = {
|
|
62
|
+
mistral: {
|
|
63
|
+
status: "native_smoke_passed",
|
|
64
|
+
mediation: "native",
|
|
65
|
+
targetVersion: "vibe 2.14.1",
|
|
66
|
+
entrypoint: { command: "vibe-acp", args: [] },
|
|
67
|
+
runtimeEnabled: false,
|
|
68
|
+
smokeSupported: true,
|
|
69
|
+
smokeStatus: "passed",
|
|
70
|
+
caveats: [
|
|
71
|
+
"Native ACP via the provider-scoped vibe-acp executable; first runtime pilot.",
|
|
72
|
+
"Runtime routing stays disabled until ACP is enabled in gateway config.",
|
|
73
|
+
],
|
|
74
|
+
docs: ACP_DOCS_REFERENCE,
|
|
75
|
+
},
|
|
76
|
+
grok: {
|
|
77
|
+
status: "native_smoke_passed",
|
|
78
|
+
mediation: "native",
|
|
79
|
+
targetVersion: "grok 0.2.50 (cadf94855)",
|
|
80
|
+
entrypoint: { command: "grok", args: ["agent", "stdio"] },
|
|
81
|
+
runtimeEnabled: false,
|
|
82
|
+
smokeSupported: true,
|
|
83
|
+
smokeStatus: "passed",
|
|
84
|
+
caveats: [
|
|
85
|
+
"Native ACP via grok agent stdio; second runtime pilot.",
|
|
86
|
+
"Credential lookup is owned by the installed CLI; empty-env smoke is not expected to pass.",
|
|
87
|
+
"Runtime routing stays disabled until ACP is enabled in gateway config.",
|
|
88
|
+
],
|
|
89
|
+
docs: ACP_DOCS_REFERENCE,
|
|
90
|
+
},
|
|
91
|
+
codex: {
|
|
92
|
+
status: "adapter_mediated_deferred",
|
|
93
|
+
mediation: "adapter_mediated",
|
|
94
|
+
targetVersion: "codex-cli 0.139.0",
|
|
95
|
+
entrypoint: null,
|
|
96
|
+
runtimeEnabled: false,
|
|
97
|
+
smokeSupported: false,
|
|
98
|
+
smokeStatus: "unsupported",
|
|
99
|
+
caveats: [
|
|
100
|
+
"No native ACP entrypoint at the target version; ACP would be adapter-mediated.",
|
|
101
|
+
"Adapter support requires a separate threat model and is never labelled native gateway ACP support.",
|
|
102
|
+
],
|
|
103
|
+
docs: ACP_DOCS_REFERENCE,
|
|
104
|
+
},
|
|
105
|
+
claude: {
|
|
106
|
+
status: "adapter_mediated_deferred",
|
|
107
|
+
mediation: "adapter_mediated",
|
|
108
|
+
targetVersion: "claude 2.1.175",
|
|
109
|
+
entrypoint: null,
|
|
110
|
+
runtimeEnabled: false,
|
|
111
|
+
smokeSupported: false,
|
|
112
|
+
smokeStatus: "unsupported",
|
|
113
|
+
caveats: [
|
|
114
|
+
"No native Claude Code CLI ACP entrypoint at the target version; ACP would be adapter-mediated.",
|
|
115
|
+
"Adapter ownership, permission bridging, and install story must be specified before runtime support.",
|
|
116
|
+
],
|
|
117
|
+
docs: ACP_DOCS_REFERENCE,
|
|
118
|
+
},
|
|
119
|
+
gemini: {
|
|
120
|
+
status: "absent_watchlist",
|
|
121
|
+
mediation: "none",
|
|
122
|
+
targetVersion: "agy 1.0.7",
|
|
123
|
+
entrypoint: null,
|
|
124
|
+
runtimeEnabled: false,
|
|
125
|
+
smokeSupported: false,
|
|
126
|
+
smokeStatus: "unsupported",
|
|
127
|
+
caveats: [
|
|
128
|
+
"Antigravity agy 1.0.7 has no ACP flag or subcommand.",
|
|
129
|
+
"Legacy Gemini CLI ACP evidence does not transfer to agy; kept on the upstream drift watchlist.",
|
|
130
|
+
],
|
|
131
|
+
docs: ACP_DOCS_REFERENCE,
|
|
132
|
+
},
|
|
133
|
+
grok_api: {
|
|
134
|
+
status: "not_applicable",
|
|
135
|
+
mediation: "none",
|
|
136
|
+
targetVersion: "xAI Responses API",
|
|
137
|
+
entrypoint: null,
|
|
138
|
+
runtimeEnabled: false,
|
|
139
|
+
smokeSupported: false,
|
|
140
|
+
smokeStatus: "unsupported",
|
|
141
|
+
caveats: ["ACP is a CLI-stdio transport; the HTTP API provider has no ACP surface."],
|
|
142
|
+
docs: ACP_DOCS_REFERENCE,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
function cloneAcpCapability(acp) {
|
|
146
|
+
return {
|
|
147
|
+
...acp,
|
|
148
|
+
entrypoint: acp.entrypoint
|
|
149
|
+
? { command: acp.entrypoint.command, args: [...acp.entrypoint.args] }
|
|
150
|
+
: null,
|
|
151
|
+
caveats: [...acp.caveats],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
14
154
|
const PROVIDER_CAPABILITY_IDS = [...CLI_TYPES, "grok_api"];
|
|
15
155
|
const KNOWN_PROVIDER_TOOLS = {
|
|
16
156
|
grok: [
|
|
@@ -640,6 +780,8 @@ function buildOneProviderToolCapabilities(cli, query) {
|
|
|
640
780
|
gatewayRequestTool: gatewayRequestTools[0] ?? definition.gatewayRequestTools[0],
|
|
641
781
|
modelInfo: getModelInfo(cli, query.refresh),
|
|
642
782
|
summary: definition.summary,
|
|
783
|
+
acpContract: { ...ACP_CONTRACT.providers[cli] },
|
|
784
|
+
acp: cloneAcpCapability(ACP_CAPABILITIES[cli]),
|
|
643
785
|
controls: cloneControls(definition.controls),
|
|
644
786
|
features,
|
|
645
787
|
discoveredSkills,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export interface GatewayRequestContext {
|
|
2
|
+
transport?: "stdio" | "http";
|
|
2
3
|
authKind?: "disabled" | "gateway_bearer" | "oauth";
|
|
3
4
|
authScopes: string[];
|
|
4
5
|
authClientId?: string;
|
|
6
|
+
authPrincipal?: string;
|
|
5
7
|
}
|
|
8
|
+
export declare function resolveOwnerPrincipal(ctx: GatewayRequestContext | undefined): string;
|
|
9
|
+
export declare function principalCanAccess(rowOwner: string | null | undefined, caller: string): boolean;
|
|
6
10
|
export declare function runWithRequestContext<T>(context: GatewayRequestContext, callback: () => T | Promise<T>): T | Promise<T>;
|
|
7
11
|
export declare function getRequestContext(): GatewayRequestContext | undefined;
|
package/dist/request-context.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
const requestContext = new AsyncLocalStorage();
|
|
3
|
+
export function resolveOwnerPrincipal(ctx) {
|
|
4
|
+
if (!ctx)
|
|
5
|
+
return "local";
|
|
6
|
+
if (ctx.authPrincipal)
|
|
7
|
+
return ctx.authPrincipal;
|
|
8
|
+
if (ctx.authKind === "gateway_bearer")
|
|
9
|
+
return "gateway-bearer";
|
|
10
|
+
return "local";
|
|
11
|
+
}
|
|
12
|
+
export function principalCanAccess(rowOwner, caller) {
|
|
13
|
+
if (rowOwner === caller)
|
|
14
|
+
return true;
|
|
15
|
+
if ((rowOwner === null || rowOwner === undefined) && caller === "local")
|
|
16
|
+
return true;
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
3
19
|
export function runWithRequestContext(context, callback) {
|
|
4
20
|
return requestContext.run(context, callback);
|
|
5
21
|
}
|
|
@@ -98,8 +98,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
98
98
|
effort: z.ZodOptional<z.ZodEnum<["low", "medium", "high", "xhigh", "max"]>>;
|
|
99
99
|
excludeDynamicSystemPromptSections: z.ZodOptional<z.ZodBoolean>;
|
|
100
100
|
}, "strip", z.ZodTypeAny, {
|
|
101
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
102
101
|
agent?: string | undefined;
|
|
102
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
103
103
|
forkSession?: boolean | undefined;
|
|
104
104
|
systemPrompt?: string | undefined;
|
|
105
105
|
appendSystemPrompt?: string | undefined;
|
|
@@ -108,8 +108,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
108
108
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
109
109
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
110
110
|
}, {
|
|
111
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
112
111
|
agent?: string | undefined;
|
|
112
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
113
113
|
forkSession?: boolean | undefined;
|
|
114
114
|
systemPrompt?: string | undefined;
|
|
115
115
|
appendSystemPrompt?: string | undefined;
|
|
@@ -118,8 +118,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
118
118
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
119
119
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
120
120
|
}>, {
|
|
121
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
122
121
|
agent?: string | undefined;
|
|
122
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
123
123
|
forkSession?: boolean | undefined;
|
|
124
124
|
systemPrompt?: string | undefined;
|
|
125
125
|
appendSystemPrompt?: string | undefined;
|
|
@@ -128,8 +128,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
128
128
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
129
129
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
130
130
|
}, {
|
|
131
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
132
131
|
agent?: string | undefined;
|
|
132
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
133
133
|
forkSession?: boolean | undefined;
|
|
134
134
|
systemPrompt?: string | undefined;
|
|
135
135
|
appendSystemPrompt?: string | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
export declare class PayloadTooLargeError extends Error {
|
|
3
|
+
readonly statusCode = 413;
|
|
4
|
+
constructor(maxBytes: number);
|
|
5
|
+
}
|
|
6
|
+
export declare function maxHttpBodyBytes(env?: NodeJS.ProcessEnv): number;
|
|
7
|
+
export declare function maxOAuthBodyBytes(env?: NodeJS.ProcessEnv): number;
|
|
8
|
+
export declare function readCappedRawBody(req: IncomingMessage, maxBytes: number): Promise<string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class PayloadTooLargeError extends Error {
|
|
2
|
+
statusCode = 413;
|
|
3
|
+
constructor(maxBytes) {
|
|
4
|
+
super(`Request body exceeds maximum size (${maxBytes} bytes)`);
|
|
5
|
+
this.name = "PayloadTooLargeError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const DEFAULT_MAX_HTTP_BODY_BYTES = 8 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_MAX_OAUTH_BODY_BYTES = 64 * 1024;
|
|
10
|
+
function positiveIntFromEnv(value, fallback) {
|
|
11
|
+
const parsed = Number(value);
|
|
12
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
|
|
13
|
+
}
|
|
14
|
+
export function maxHttpBodyBytes(env = process.env) {
|
|
15
|
+
return positiveIntFromEnv(env.LLM_GATEWAY_MAX_HTTP_BODY_BYTES, DEFAULT_MAX_HTTP_BODY_BYTES);
|
|
16
|
+
}
|
|
17
|
+
export function maxOAuthBodyBytes(env = process.env) {
|
|
18
|
+
return positiveIntFromEnv(env.LLM_GATEWAY_MAX_OAUTH_BODY_BYTES, DEFAULT_MAX_OAUTH_BODY_BYTES);
|
|
19
|
+
}
|
|
20
|
+
export function readCappedRawBody(req, maxBytes) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const chunks = [];
|
|
23
|
+
let total = 0;
|
|
24
|
+
let aborted = false;
|
|
25
|
+
req.on("data", (chunk) => {
|
|
26
|
+
if (aborted)
|
|
27
|
+
return;
|
|
28
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
29
|
+
total += buf.length;
|
|
30
|
+
if (total > maxBytes) {
|
|
31
|
+
aborted = true;
|
|
32
|
+
chunks.length = 0;
|
|
33
|
+
reject(new PayloadTooLargeError(maxBytes));
|
|
34
|
+
req.resume();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
chunks.push(buf);
|
|
38
|
+
});
|
|
39
|
+
req.on("error", err => {
|
|
40
|
+
if (!aborted)
|
|
41
|
+
reject(err);
|
|
42
|
+
});
|
|
43
|
+
req.on("end", () => {
|
|
44
|
+
if (aborted)
|
|
45
|
+
return;
|
|
46
|
+
resolve(chunks.length === 0 ? "" : Buffer.concat(chunks).toString("utf8"));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const REDACTED = "[REDACTED]";
|
|
2
|
+
const RULES = [
|
|
3
|
+
{
|
|
4
|
+
label: "private-key",
|
|
5
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----/g,
|
|
6
|
+
},
|
|
7
|
+
{ label: "anthropic-key", pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}/g },
|
|
8
|
+
{ label: "openai-key", pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}/g },
|
|
9
|
+
{ label: "xai-key", pattern: /\bxai-[A-Za-z0-9]{16,}/g },
|
|
10
|
+
{ label: "google-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
11
|
+
{ label: "aws-access-key-id", pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
|
|
12
|
+
{ label: "github-token", pattern: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
|
|
13
|
+
{ label: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}/g },
|
|
14
|
+
{
|
|
15
|
+
label: "jwt",
|
|
16
|
+
pattern: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "bearer",
|
|
20
|
+
pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/g,
|
|
21
|
+
replace: () => `Bearer ${REDACTED}`,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
label: "url-credential",
|
|
25
|
+
pattern: /([a-z][a-z0-9+.-]*:\/\/[^\s/:@]+):[^\s/@]+@/gi,
|
|
26
|
+
replace: (_m, prefix) => `${prefix}:${REDACTED}@`,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: "secret-assignment",
|
|
30
|
+
pattern: /\b(password|passwd|pwd|secret[_-]?key|client[_-]?secret|api[_-]?key|access[_-]?key|auth[_-]?token)\b(\s*[:=]\s*)(?:"[^"\n]{6,}"|'[^'\n]{6,}'|[^\s"'\n,;]{6,})/gi,
|
|
31
|
+
replace: (_m, key, sep) => `${key}${sep}${REDACTED}`,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
export function isRedactionEnabled(env = process.env) {
|
|
35
|
+
const raw = (env.LLM_GATEWAY_REDACT_LOGGED_SECRETS ?? "").trim().toLowerCase();
|
|
36
|
+
return raw !== "0" && raw !== "false" && raw !== "off" && raw !== "no";
|
|
37
|
+
}
|
|
38
|
+
export function redactSecrets(text) {
|
|
39
|
+
if (!text)
|
|
40
|
+
return text;
|
|
41
|
+
let out = text;
|
|
42
|
+
for (const rule of RULES) {
|
|
43
|
+
out = rule.replace
|
|
44
|
+
? out.replace(rule.pattern, rule.replace)
|
|
45
|
+
: out.replace(rule.pattern, REDACTED);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
export function redactIfEnabled(value, enabled) {
|
|
50
|
+
if (!enabled || !value)
|
|
51
|
+
return value;
|
|
52
|
+
return redactSecrets(value);
|
|
53
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
+
import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
|
|
2
3
|
const DEFAULT_SESSION_DESCRIPTIONS = {
|
|
3
4
|
claude: "Claude Session",
|
|
4
5
|
codex: "Codex Session",
|
|
@@ -16,11 +17,12 @@ export class PostgreSQLSessionManager {
|
|
|
16
17
|
const id = sessionId || randomUUID();
|
|
17
18
|
const sessionDescription = description ?? DEFAULT_SESSION_DESCRIPTIONS[cli];
|
|
18
19
|
const now = new Date().toISOString();
|
|
20
|
+
const ownerPrincipal = resolveOwnerPrincipal(getRequestContext());
|
|
19
21
|
const client = await this.pool.connect();
|
|
20
22
|
try {
|
|
21
23
|
await client.query("BEGIN");
|
|
22
|
-
await client.query(`INSERT INTO sessions (id, cli, description, created_at, last_used_at)
|
|
23
|
-
VALUES ($1, $2, $3, $4, $5)`, [id, cli, sessionDescription, now, now]);
|
|
24
|
+
await client.query(`INSERT INTO sessions (id, cli, description, created_at, last_used_at, owner_principal)
|
|
25
|
+
VALUES ($1, $2, $3, $4, $5, $6)`, [id, cli, sessionDescription, now, now, ownerPrincipal]);
|
|
24
26
|
await client.query(`INSERT INTO active_sessions (cli, session_id, updated_at)
|
|
25
27
|
VALUES ($1, $2, $3)
|
|
26
28
|
ON CONFLICT (cli) DO NOTHING`, [cli, id, now]);
|
|
@@ -31,6 +33,7 @@ export class PostgreSQLSessionManager {
|
|
|
31
33
|
createdAt: now,
|
|
32
34
|
lastUsedAt: now,
|
|
33
35
|
description: sessionDescription,
|
|
36
|
+
ownerPrincipal,
|
|
34
37
|
};
|
|
35
38
|
}
|
|
36
39
|
catch (error) {
|
|
@@ -42,18 +45,18 @@ export class PostgreSQLSessionManager {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
async getSession(sessionId) {
|
|
45
|
-
const result = await this.pool.query(`SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
|
|
48
|
+
const result = await this.pool.query(`SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
|
|
46
49
|
FROM sessions
|
|
47
50
|
WHERE id = $1`, [sessionId]);
|
|
48
51
|
return result.rows[0] ?? null;
|
|
49
52
|
}
|
|
50
53
|
async listSessions(cli) {
|
|
51
54
|
const query = cli
|
|
52
|
-
? `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
|
|
55
|
+
? `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
|
|
53
56
|
FROM sessions
|
|
54
57
|
WHERE cli = $1
|
|
55
58
|
ORDER BY last_used_at DESC`
|
|
56
|
-
: `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt"
|
|
59
|
+
: `SELECT id, cli, description, metadata, created_at AS "createdAt", last_used_at AS "lastUsedAt", owner_principal AS "ownerPrincipal"
|
|
57
60
|
FROM sessions
|
|
58
61
|
ORDER BY last_used_at DESC`;
|
|
59
62
|
const result = cli
|
package/dist/session-manager.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join, dirname } from "path";
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, openSync, fsyncSync, closeSync, chmodSync, } from "fs";
|
|
5
5
|
import { DEFAULT_SESSION_TTL_SECONDS } from "./config.js";
|
|
6
6
|
import { noopLogger } from "./logger.js";
|
|
7
|
+
import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
|
|
7
8
|
export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral"];
|
|
8
9
|
export const API_PROVIDER_TYPES = ["grok-api"];
|
|
9
10
|
export const PROVIDER_TYPES = [...CLI_TYPES, ...API_PROVIDER_TYPES];
|
|
@@ -113,6 +114,7 @@ export class FileSessionManager {
|
|
|
113
114
|
createdAt: new Date().toISOString(),
|
|
114
115
|
lastUsedAt: new Date().toISOString(),
|
|
115
116
|
description: sessionDescription,
|
|
117
|
+
ownerPrincipal: resolveOwnerPrincipal(getRequestContext()),
|
|
116
118
|
};
|
|
117
119
|
this.storage.sessions[id] = session;
|
|
118
120
|
if (!this.storage.activeSession[cli]) {
|
|
@@ -107,6 +107,20 @@ export interface ProviderSubcommandCompactCatalog {
|
|
|
107
107
|
];
|
|
108
108
|
rows: readonly (readonly string[])[];
|
|
109
109
|
}
|
|
110
|
+
export type AcpEntrypointStatus = "native" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
111
|
+
export interface AcpEntrypointContract {
|
|
112
|
+
cli: CliType;
|
|
113
|
+
displayName: string;
|
|
114
|
+
status: AcpEntrypointStatus;
|
|
115
|
+
executable: string;
|
|
116
|
+
entrypointArgs: readonly string[];
|
|
117
|
+
targetVersion: string;
|
|
118
|
+
probeArgs: readonly (readonly string[])[];
|
|
119
|
+
adapterCandidates?: readonly string[];
|
|
120
|
+
evidence: string;
|
|
121
|
+
docsRef: string;
|
|
122
|
+
}
|
|
123
|
+
export declare const ACP_ENTRYPOINT_CONTRACTS: Record<CliType, AcpEntrypointContract>;
|
|
110
124
|
export declare const UPSTREAM_CLI_CONTRACTS: Record<CliType, CliContract>;
|
|
111
125
|
export declare function validateUpstreamCliArgs(cli: CliType, args: readonly string[]): ContractValidationResult;
|
|
112
126
|
export declare function assertUpstreamCliArgs(cli: CliType, args: readonly string[]): void;
|
|
@@ -167,6 +181,19 @@ export interface InstalledCliContractProbe {
|
|
|
167
181
|
warnings: string[];
|
|
168
182
|
}
|
|
169
183
|
export declare function probeInstalledCliContract(cli: CliType, timeoutMs?: number): InstalledCliContractProbe;
|
|
184
|
+
export interface InstalledAcpEntrypointProbe {
|
|
185
|
+
cli: CliType;
|
|
186
|
+
status: AcpEntrypointStatus;
|
|
187
|
+
executable: string;
|
|
188
|
+
entrypointArgs: readonly string[];
|
|
189
|
+
targetVersion: string;
|
|
190
|
+
checkedProbeCommands: readonly (readonly string[])[];
|
|
191
|
+
available: boolean | null;
|
|
192
|
+
entrypointDrift: boolean;
|
|
193
|
+
warnings: string[];
|
|
194
|
+
probedAt: string;
|
|
195
|
+
}
|
|
196
|
+
export declare function probeInstalledAcpEntrypoint(cli: CliType, timeoutMs?: number): InstalledAcpEntrypointProbe;
|
|
170
197
|
export declare function buildUpstreamContractReport(options?: {
|
|
171
198
|
cli?: CliType;
|
|
172
199
|
probeInstalled?: boolean;
|