@skelm/codex 0.3.9
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 +131 -0
- package/dist/backend.d.ts +21 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +208 -0
- package/dist/backend.js.map +1 -0
- package/dist/client.d.ts +61 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +154 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/permission-mapper.d.ts +65 -0
- package/dist/permission-mapper.d.ts.map +1 -0
- package/dist/permission-mapper.js +176 -0
- package/dist/permission-mapper.js.map +1 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Scott Glover
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @skelm/codex
|
|
2
|
+
|
|
3
|
+
> OpenAI Codex backend for [skelm](https://github.com/scottgl9/skelm) — wraps the official [`@openai/codex-sdk`](https://github.com/openai/codex/blob/main/sdk/typescript/README.md) with full skelm permission enforcement, MCP injection, skill loading, and streaming.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@skelm/codex)
|
|
6
|
+
|
|
7
|
+
Part of [skelm](https://github.com/scottgl9/skelm).
|
|
8
|
+
|
|
9
|
+
Codex authenticates via the host `codex` CLI (`codex login`) or the `CODEX_API_KEY` env var. The SDK spawns codex under the hood and exchanges JSONL events — skelm enforces permissions at the boundary, pins the workspace, optionally routes egress through the gateway's CONNECT proxy, and emits audit events as Codex completes commands, file changes, and MCP tool calls.
|
|
10
|
+
|
|
11
|
+
| Capability | Value |
|
|
12
|
+
|--------------------|----------------------------------------------------------------|
|
|
13
|
+
| `prompt` | `false` (codex-sdk is agent-loop only) |
|
|
14
|
+
| `streaming` | `true` (`agent_message` text deltas flow to `onPartial`) |
|
|
15
|
+
| `sessionLifecycle` | `true` (`request.sessionId` → `Codex.resumeThread`) |
|
|
16
|
+
| `mcp` | `true` (skelm MCP servers injected via `config.mcp_servers`) |
|
|
17
|
+
| `skills` | `true` (skill bodies concatenated into the system prompt) |
|
|
18
|
+
| `toolPermissions` | `'native'` (Codex enforces sandbox / approval / network in its own process; skelm checks at the boundary) |
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
|
|
22
|
+
- `codex` CLI on PATH (`codex --version` ≥ 0.130.0)
|
|
23
|
+
- Authenticated session — `codex login` once, or `CODEX_API_KEY` in env
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install @skelm/codex
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
// skelm.config.ts
|
|
35
|
+
import { defineConfig } from 'skelm'
|
|
36
|
+
|
|
37
|
+
export default defineConfig({
|
|
38
|
+
backends: {
|
|
39
|
+
agent: 'codex',
|
|
40
|
+
codex: {
|
|
41
|
+
model: 'gpt-5.3-codex',
|
|
42
|
+
modelReasoningEffort: 'medium',
|
|
43
|
+
skipGitRepoCheck: true,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaults: {
|
|
47
|
+
permissions: {
|
|
48
|
+
networkEgress: 'deny',
|
|
49
|
+
allowedTools: [],
|
|
50
|
+
allowedExecutables: [],
|
|
51
|
+
allowedSkills: [],
|
|
52
|
+
allowedMcpServers: [],
|
|
53
|
+
fsRead: [],
|
|
54
|
+
fsWrite: [],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// codex-smoke.pipeline.ts
|
|
62
|
+
import { agent, pipeline } from 'skelm'
|
|
63
|
+
import { z } from 'zod'
|
|
64
|
+
|
|
65
|
+
export default pipeline({
|
|
66
|
+
id: 'codex-smoke',
|
|
67
|
+
input: z.object({ task: z.string() }),
|
|
68
|
+
output: z.object({ result: z.string() }),
|
|
69
|
+
steps: [
|
|
70
|
+
agent({
|
|
71
|
+
id: 'work',
|
|
72
|
+
backend: 'codex',
|
|
73
|
+
prompt: (ctx) => (ctx.input as { task: string }).task,
|
|
74
|
+
permissions: {
|
|
75
|
+
// For a real read-write workflow grant fsWrite roots + relevant tools.
|
|
76
|
+
// The mapper refuses fsWrite: ['*'] unless approval policy is empty.
|
|
77
|
+
fsRead: [],
|
|
78
|
+
fsWrite: [],
|
|
79
|
+
networkEgress: 'deny',
|
|
80
|
+
},
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
skelm run codex-smoke.pipeline.ts --input '{"task":"say ok"}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Permission mapping
|
|
91
|
+
|
|
92
|
+
The boundary-time mapper translates a resolved skelm policy into Codex SDK options. If the policy can't be honored safely, it throws `CodexPermissionError` before any Codex invocation.
|
|
93
|
+
|
|
94
|
+
| Skelm policy | Codex SDK option |
|
|
95
|
+
|---------------------------------------------|---------------------------------------------------------------|
|
|
96
|
+
| `fsWrite: []`, `fsRead: []` | `sandboxMode: 'read-only'` |
|
|
97
|
+
| `fsWrite: [<roots>]` | `sandboxMode: 'workspace-write'`, first root → `workingDirectory`, rest → `additionalDirectories` |
|
|
98
|
+
| `request.cwd` set | overrides `workingDirectory` |
|
|
99
|
+
| `fsWrite: ['*']` AND no approval policy | `sandboxMode: 'danger-full-access'` |
|
|
100
|
+
| `fsWrite: ['*']` AND approval policy set | **refused** — never silently escalate |
|
|
101
|
+
| `networkEgress: 'deny'` | `networkAccessEnabled: false` |
|
|
102
|
+
| `networkEgress: 'allow'` or `{ allowHosts }` | `networkAccessEnabled: true` (gateway proxy enforces hosts) |
|
|
103
|
+
| `approval.on` covers `tool` / `executable` | `approvalPolicy: 'untrusted'` |
|
|
104
|
+
| anything else | `approvalPolicy: 'on-request'` |
|
|
105
|
+
|
|
106
|
+
## MCP, skills, streaming
|
|
107
|
+
|
|
108
|
+
- **MCP** — `request.mcpServers` is filtered by `policy.allowedMcpServers`, then passed to `Codex({ config: { mcp_servers: { … } } })`. Stdio transports are translated today; HTTP/SSE transports are dropped with `permission.denied` audit so the gap is visible.
|
|
109
|
+
- **Skills** — When `request.skills` is set and the policy permits, the backend calls `context.loadSkill(id)` for each id and concatenates the formatted skill blocks into the system prompt.
|
|
110
|
+
- **Streaming** — `agent_message.text` deltas flow to `BackendContext.onPartial`. `command_execution`, `file_change`, and `mcp_tool_call` items surface via `onItem` for audit emission.
|
|
111
|
+
|
|
112
|
+
## API surface
|
|
113
|
+
|
|
114
|
+
- `createCodexBackend(options?: CodexBackendOptions): SkelmBackend` — the factory.
|
|
115
|
+
- `mapPermissionsToCodex({ policy, workingDirectory })` — boundary mapper; throws `CodexPermissionError` on unsafe widening.
|
|
116
|
+
- `buildAuditEntry(...)` — hash-chained-audit-ready record of the mapping decision.
|
|
117
|
+
- `filterIds(ids, allowlist)` — partition step-requested ids by an allowlist.
|
|
118
|
+
- Re-exports: `CodexPermissionError`, types `CodexBackendOptions`, `MappedCodexPolicy`, `CodexPermissionAuditEntry`.
|
|
119
|
+
|
|
120
|
+
## Live integration test
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
codex login
|
|
124
|
+
SKELM_CODEX_INTEGRATION=1 pnpm test packages/codex/test/integration.test.ts
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The skill-injection test registers a `magic-word` skill and asserts the agent surfaces the skill-provided answer — a real end-to-end verification.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SkelmBackend } from '@skelm/core';
|
|
2
|
+
import type { CodexBackendOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* SkelmBackend for OpenAI Codex via the official `@openai/codex-sdk`.
|
|
5
|
+
*
|
|
6
|
+
* Surfaces the full skelm feature set against Codex:
|
|
7
|
+
*
|
|
8
|
+
* - MCP servers injected through `CodexOptions.config.mcp_servers`.
|
|
9
|
+
* - Skills concatenated into the system prompt before each turn.
|
|
10
|
+
* - Permissions translated to Codex sandbox + approval modes.
|
|
11
|
+
* - Streaming events relayed via `BackendContext.onPartial`.
|
|
12
|
+
* - Session resumption via `Codex.resumeThread`.
|
|
13
|
+
* - Cancellation via the SDK's per-turn `signal: AbortSignal`.
|
|
14
|
+
*
|
|
15
|
+
* Permission enforcement is `'native'`: Codex enforces sandbox / approval /
|
|
16
|
+
* network natively in its own process. Skelm validates at the boundary
|
|
17
|
+
* (pre-run refusal, workspace pinning, egress proxy envelope, post-event
|
|
18
|
+
* audit) — never widening what the policy permits.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createCodexBackend(options?: CodexBackendOptions): SkelmBackend;
|
|
21
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAOV,YAAY,EACb,MAAM,aAAa,CAAA;AAUpB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CA+HlF"}
|
package/dist/backend.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { buildSystemPromptFromRequest, loadSkillBodies, resolvePermissions } from '@skelm/core';
|
|
2
|
+
import { buildCodexOptions, buildMcpServerConfig, buildThreadOptions, consumeStream, makeCodexClient, } from './client.js';
|
|
3
|
+
import { mapPermissionsToCodex } from './permission-mapper.js';
|
|
4
|
+
/**
|
|
5
|
+
* SkelmBackend for OpenAI Codex via the official `@openai/codex-sdk`.
|
|
6
|
+
*
|
|
7
|
+
* Surfaces the full skelm feature set against Codex:
|
|
8
|
+
*
|
|
9
|
+
* - MCP servers injected through `CodexOptions.config.mcp_servers`.
|
|
10
|
+
* - Skills concatenated into the system prompt before each turn.
|
|
11
|
+
* - Permissions translated to Codex sandbox + approval modes.
|
|
12
|
+
* - Streaming events relayed via `BackendContext.onPartial`.
|
|
13
|
+
* - Session resumption via `Codex.resumeThread`.
|
|
14
|
+
* - Cancellation via the SDK's per-turn `signal: AbortSignal`.
|
|
15
|
+
*
|
|
16
|
+
* Permission enforcement is `'native'`: Codex enforces sandbox / approval /
|
|
17
|
+
* network natively in its own process. Skelm validates at the boundary
|
|
18
|
+
* (pre-run refusal, workspace pinning, egress proxy envelope, post-event
|
|
19
|
+
* audit) — never widening what the policy permits.
|
|
20
|
+
*/
|
|
21
|
+
export function createCodexBackend(options = {}) {
|
|
22
|
+
const capabilities = {
|
|
23
|
+
prompt: false,
|
|
24
|
+
streaming: true,
|
|
25
|
+
sessionLifecycle: true,
|
|
26
|
+
mcp: true,
|
|
27
|
+
skills: true,
|
|
28
|
+
modelSelection: options.model !== undefined,
|
|
29
|
+
// Codex enforces sandbox / approval / network natively in its own process.
|
|
30
|
+
// Skelm checks at the boundary (refusing unsafe combinations before any
|
|
31
|
+
// Codex call); Codex enforces at runtime.
|
|
32
|
+
toolPermissions: 'native',
|
|
33
|
+
};
|
|
34
|
+
const backend = {
|
|
35
|
+
id: options.id ?? 'codex',
|
|
36
|
+
capabilities,
|
|
37
|
+
...(options.label !== undefined && { label: options.label }),
|
|
38
|
+
async run(request, context) {
|
|
39
|
+
// When neither the request nor the context carries a resolved policy,
|
|
40
|
+
// synthesize an empty-deny ResolvedPolicy from `resolvePermissions(
|
|
41
|
+
// undefined, undefined)`. This matches the documented default-deny
|
|
42
|
+
// intent of skelm: an omitted policy must deny everything, not crash
|
|
43
|
+
// the run. Previously we threw "codex backend requires a resolved
|
|
44
|
+
// permission policy" here, which forced every codex agent() step to
|
|
45
|
+
// restate the same empty allowlists even when config-level defaults
|
|
46
|
+
// were set. The mapper below still translates this into Codex's
|
|
47
|
+
// strictest sandbox/approval/network settings.
|
|
48
|
+
const policy = request.permissions ?? context.permissions ?? resolvePermissions(undefined, undefined);
|
|
49
|
+
// Boundary check + sandbox/approval translation. Throws on refusal.
|
|
50
|
+
const mapped = mapPermissionsToCodex({
|
|
51
|
+
policy,
|
|
52
|
+
...(request.cwd !== undefined && { workingDirectory: request.cwd }),
|
|
53
|
+
});
|
|
54
|
+
// Filter requested MCP servers through the allowlist.
|
|
55
|
+
const allowed = filterAllowedMcp(request.mcpServers, policy.allowedMcpServers);
|
|
56
|
+
const mcpConfig = buildMcpServerConfig(allowed.allowed);
|
|
57
|
+
const deniedMcp = allowed.denied.map((s) => s.id);
|
|
58
|
+
// Audit-only for now; the runner's audit writer is the durable record.
|
|
59
|
+
const logDenial = (dimension, ids, reason) => console.warn(JSON.stringify({ event: 'permission.denied', dimension, ids, reason, backend: 'codex' }));
|
|
60
|
+
if (deniedMcp.length > 0) {
|
|
61
|
+
logDenial('mcp', deniedMcp, 'not-in-allowlist');
|
|
62
|
+
}
|
|
63
|
+
if (mcpConfig !== null && mcpConfig.dropped.length > 0) {
|
|
64
|
+
logDenial('mcp', mcpConfig.dropped, 'transport-unsupported');
|
|
65
|
+
}
|
|
66
|
+
// Construct the SDK client with config + proxy env. Only forward
|
|
67
|
+
// `mcp_servers` to Codex — never leak the `dropped` bookkeeping field.
|
|
68
|
+
const codexOpts = buildCodexOptions(options, {
|
|
69
|
+
...(context.proxyEnv !== undefined && { env: context.proxyEnv }),
|
|
70
|
+
...(mcpConfig !== null && { config: { mcp_servers: mcpConfig.mcp_servers } }),
|
|
71
|
+
});
|
|
72
|
+
const codex = makeCodexClient(codexOpts);
|
|
73
|
+
// Compose the system prompt via @skelm/core's shared builder so
|
|
74
|
+
// systemPromptMode / systemPromptIncludeAgentDef take effect here.
|
|
75
|
+
const systemPrompt = await composeSystemPrompt(request, context, options.model);
|
|
76
|
+
const userPrompt = systemPrompt === undefined ? request.prompt : `${systemPrompt}\n\n---\n\n${request.prompt}`;
|
|
77
|
+
// Build the thread (resume vs fresh) honoring per-step sandbox/approval.
|
|
78
|
+
const threadOpts = buildThreadOptions(options, {
|
|
79
|
+
sandboxMode: mapped.sandboxMode,
|
|
80
|
+
approvalPolicy: mapped.approvalPolicy,
|
|
81
|
+
networkAccessEnabled: mapped.networkAccessEnabled,
|
|
82
|
+
webSearchEnabled: mapped.webSearchEnabled,
|
|
83
|
+
webSearchMode: mapped.webSearchMode,
|
|
84
|
+
...(mapped.workingDirectory !== undefined && {
|
|
85
|
+
workingDirectory: mapped.workingDirectory,
|
|
86
|
+
}),
|
|
87
|
+
...(mapped.additionalDirectories !== undefined && {
|
|
88
|
+
additionalDirectories: mapped.additionalDirectories,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
const sessionId = readSessionId(request);
|
|
92
|
+
const thread = sessionId !== undefined
|
|
93
|
+
? codex.resumeThread(sessionId, threadOpts)
|
|
94
|
+
: codex.startThread(threadOpts);
|
|
95
|
+
// Compose abort: the runner-supplied `context.signal` AND the
|
|
96
|
+
// backend's `timeoutMs` (defensive ceiling). The SDK honors a single
|
|
97
|
+
// AbortSignal on TurnOptions natively.
|
|
98
|
+
const turnSignal = composeAbortSignal(context.signal, options.timeoutMs ?? 300_000);
|
|
99
|
+
const { events } = await thread.runStreamed(userPrompt, {
|
|
100
|
+
...(request.outputSchema !== undefined && { outputSchema: request.outputSchema }),
|
|
101
|
+
signal: turnSignal.signal,
|
|
102
|
+
});
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = await consumeStream(events, {
|
|
106
|
+
...(context.onPartial !== undefined && { onText: context.onPartial }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
turnSignal.cancel();
|
|
111
|
+
}
|
|
112
|
+
const response = {
|
|
113
|
+
text: result.finalText,
|
|
114
|
+
stopReason: result.stopReason,
|
|
115
|
+
};
|
|
116
|
+
if (result.usage !== undefined) {
|
|
117
|
+
response.usage = {
|
|
118
|
+
...(result.usage.inputTokens !== undefined && { inputTokens: result.usage.inputTokens }),
|
|
119
|
+
...(result.usage.outputTokens !== undefined && {
|
|
120
|
+
outputTokens: result.usage.outputTokens,
|
|
121
|
+
}),
|
|
122
|
+
...(result.usage.reasoningTokens !== undefined && {
|
|
123
|
+
reasoningTokens: result.usage.reasoningTokens,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return response;
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
return backend;
|
|
131
|
+
}
|
|
132
|
+
function filterAllowedMcp(servers, allowlist) {
|
|
133
|
+
if (servers === undefined || servers.length === 0)
|
|
134
|
+
return { allowed: [], denied: [] };
|
|
135
|
+
const allowed = [];
|
|
136
|
+
const denied = [];
|
|
137
|
+
for (const s of servers) {
|
|
138
|
+
if (allowlist.has(s.id))
|
|
139
|
+
allowed.push(s);
|
|
140
|
+
else
|
|
141
|
+
denied.push(s);
|
|
142
|
+
}
|
|
143
|
+
return { allowed, denied };
|
|
144
|
+
}
|
|
145
|
+
async function composeSystemPrompt(req, ctx, model) {
|
|
146
|
+
// Route through @skelm/core's shared builder so `systemPromptMode` and
|
|
147
|
+
// `systemPromptIncludeAgentDef` actually take effect on codex. Without
|
|
148
|
+
// this, codex previously hand-rolled the prompt out of soul +
|
|
149
|
+
// instructions + system + skills, ignoring both flags — the same input
|
|
150
|
+
// produced identical token counts in extend vs replace mode.
|
|
151
|
+
//
|
|
152
|
+
// The builder owns the "extend" vs "replace" composition and the
|
|
153
|
+
// "include AGENTS.md/SOUL.md when replacing" carve-out. We pass an
|
|
154
|
+
// empty tool list because codex enforces its tool surface natively and
|
|
155
|
+
// skelm doesn't dispatch tools through this backend — there's no
|
|
156
|
+
// skelm-side tool inventory worth injecting.
|
|
157
|
+
const base = buildSystemPromptFromRequest(req, {
|
|
158
|
+
cwd: req.cwd ?? process.cwd(),
|
|
159
|
+
platform: process.platform,
|
|
160
|
+
date: new Date().toISOString().slice(0, 10),
|
|
161
|
+
...(model !== undefined && { model }),
|
|
162
|
+
tools: [],
|
|
163
|
+
});
|
|
164
|
+
const skillBodies = await loadSkillBodies(req, ctx);
|
|
165
|
+
const parts = [];
|
|
166
|
+
if (base.length > 0)
|
|
167
|
+
parts.push(base);
|
|
168
|
+
parts.push(...skillBodies);
|
|
169
|
+
if (parts.length === 0)
|
|
170
|
+
return undefined;
|
|
171
|
+
return parts.join('\n\n---\n\n');
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* AgentRequest doesn't have a typed sessionId field at the moment, but
|
|
175
|
+
* runners may attach one through structural typing. Read defensively.
|
|
176
|
+
* TODO(@skelm/core): promote `sessionId?: string` to AgentRequest so this
|
|
177
|
+
* cast goes away.
|
|
178
|
+
*/
|
|
179
|
+
function readSessionId(request) {
|
|
180
|
+
const sid = request.sessionId;
|
|
181
|
+
return typeof sid === 'string' && sid.length > 0 ? sid : undefined;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Compose the runner's signal with a backend-side timeout. The returned
|
|
185
|
+
* signal aborts when either fires; `cancel()` clears the timer so a
|
|
186
|
+
* successful run doesn't leak it.
|
|
187
|
+
*/
|
|
188
|
+
function composeAbortSignal(upstream, timeoutMs) {
|
|
189
|
+
const controller = new AbortController();
|
|
190
|
+
if (upstream.aborted)
|
|
191
|
+
controller.abort(upstream.reason);
|
|
192
|
+
const onAbort = () => controller.abort(upstream.reason);
|
|
193
|
+
upstream.addEventListener('abort', onAbort, { once: true });
|
|
194
|
+
const timer = setTimeout(() => {
|
|
195
|
+
controller.abort(new Error(`codex backend timed out after ${timeoutMs}ms`));
|
|
196
|
+
}, timeoutMs);
|
|
197
|
+
// Don't keep the event loop alive solely for this timer.
|
|
198
|
+
if (typeof timer === 'object' && 'unref' in timer)
|
|
199
|
+
timer.unref();
|
|
200
|
+
return {
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
cancel: () => {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
upstream.removeEventListener('abort', onAbort);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `@openai/codex-sdk`'s `Codex` and `Thread`.
|
|
3
|
+
*
|
|
4
|
+
* - Owns a memoized `Codex` instance keyed by the constructed options. The
|
|
5
|
+
* SDK spawns the `codex` CLI per turn, but we want each backend instance
|
|
6
|
+
* to share auth/env/config across calls.
|
|
7
|
+
* - Translates `Codex` JSONL events into a normalized shape the backend
|
|
8
|
+
* can publish onto skelm's event bus and `onPartial` callback.
|
|
9
|
+
*/
|
|
10
|
+
import { Codex, type CodexOptions, type ThreadEvent, type ThreadOptions } from '@openai/codex-sdk';
|
|
11
|
+
import type { CodexBackendOptions } from './types.js';
|
|
12
|
+
/** Build CodexOptions from CodexBackendOptions + per-run overrides. */
|
|
13
|
+
export declare function buildCodexOptions(opts: CodexBackendOptions, overrides?: {
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
config?: Record<string, unknown>;
|
|
16
|
+
}): CodexOptions;
|
|
17
|
+
/** Build ThreadOptions for `Codex.startThread`. */
|
|
18
|
+
export declare function buildThreadOptions(opts: CodexBackendOptions, extras: Partial<ThreadOptions>): ThreadOptions;
|
|
19
|
+
/**
|
|
20
|
+
* Translate a skelm `McpServerConfig` array into the JS shape that the
|
|
21
|
+
* Codex SDK's `config.mcp_servers` accepts. Today Codex's TOML config only
|
|
22
|
+
* supports stdio MCP servers via `command` + `args` + `env`; HTTP/SSE
|
|
23
|
+
* transports are dropped with a warning so the caller can audit the gap.
|
|
24
|
+
*
|
|
25
|
+
* The caller is expected to have already filtered by `policy.allowedMcpServers`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildMcpServerConfig(servers: ReadonlyArray<{
|
|
28
|
+
id: string;
|
|
29
|
+
transport: 'stdio' | 'http' | 'sse';
|
|
30
|
+
command?: string;
|
|
31
|
+
args?: readonly string[];
|
|
32
|
+
env?: Readonly<Record<string, string>>;
|
|
33
|
+
url?: string;
|
|
34
|
+
}>): {
|
|
35
|
+
mcp_servers: Record<string, unknown>;
|
|
36
|
+
dropped: string[];
|
|
37
|
+
} | null;
|
|
38
|
+
/**
|
|
39
|
+
* Iterate the Codex event stream and dispatch normalized events. The
|
|
40
|
+
* caller decides how to surface each event (onPartial, audit emission,
|
|
41
|
+
* etc.). Returns the aggregated final assistant text and usage.
|
|
42
|
+
*/
|
|
43
|
+
export interface IterateResult {
|
|
44
|
+
finalText: string;
|
|
45
|
+
usage: {
|
|
46
|
+
inputTokens?: number;
|
|
47
|
+
outputTokens?: number;
|
|
48
|
+
reasoningTokens?: number;
|
|
49
|
+
} | undefined;
|
|
50
|
+
stopReason: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function consumeStream(events: AsyncIterable<ThreadEvent>, callbacks: {
|
|
53
|
+
onText?: (delta: string) => void;
|
|
54
|
+
onItem?: (event: Extract<ThreadEvent, {
|
|
55
|
+
type: 'item.completed';
|
|
56
|
+
}>) => void;
|
|
57
|
+
onError?: (message: string) => void;
|
|
58
|
+
}): Promise<IterateResult>;
|
|
59
|
+
/** Construct a `Codex` instance. Exposed for testing. */
|
|
60
|
+
export declare function makeCodexClient(opts: CodexOptions): Codex;
|
|
61
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAElG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,uEAAuE;AACvE,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,mBAAmB,EACzB,SAAS,GAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GACjF,YAAY,CAuCd;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAC7B,aAAa,CAQf;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,CAAC;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACtC,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAC,GACD;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAmBpE;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;IAC5F,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,EAClC,SAAS,EAAE;IACT,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE;QAAE,IAAI,EAAE,gBAAgB,CAAA;KAAE,CAAC,KAAK,IAAI,CAAA;IAC1E,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,GACA,OAAO,CAAC,aAAa,CAAC,CA6CxB;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,KAAK,CAEzD"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around `@openai/codex-sdk`'s `Codex` and `Thread`.
|
|
3
|
+
*
|
|
4
|
+
* - Owns a memoized `Codex` instance keyed by the constructed options. The
|
|
5
|
+
* SDK spawns the `codex` CLI per turn, but we want each backend instance
|
|
6
|
+
* to share auth/env/config across calls.
|
|
7
|
+
* - Translates `Codex` JSONL events into a normalized shape the backend
|
|
8
|
+
* can publish onto skelm's event bus and `onPartial` callback.
|
|
9
|
+
*/
|
|
10
|
+
import { Codex } from '@openai/codex-sdk';
|
|
11
|
+
/** Build CodexOptions from CodexBackendOptions + per-run overrides. */
|
|
12
|
+
export function buildCodexOptions(opts, overrides = {}) {
|
|
13
|
+
const out = {};
|
|
14
|
+
if (opts.codexPathOverride !== undefined)
|
|
15
|
+
out.codexPathOverride = opts.codexPathOverride;
|
|
16
|
+
if (opts.baseUrl !== undefined)
|
|
17
|
+
out.baseUrl = opts.baseUrl;
|
|
18
|
+
if (opts.apiKey !== undefined)
|
|
19
|
+
out.apiKey = opts.apiKey;
|
|
20
|
+
// env: merge skelm proxyEnv with the user's static env. The SDK does NOT
|
|
21
|
+
// inherit process.env when env is set, so we must replay any vars the
|
|
22
|
+
// codex CLI needs from the parent unless the caller opts out.
|
|
23
|
+
if (overrides.env !== undefined || opts.env !== undefined) {
|
|
24
|
+
const merged = {};
|
|
25
|
+
// Inherit common auth-relevant vars from process.env unless the user
|
|
26
|
+
// pinned env themselves.
|
|
27
|
+
if (opts.env === undefined) {
|
|
28
|
+
const passthrough = [
|
|
29
|
+
'HOME',
|
|
30
|
+
'PATH',
|
|
31
|
+
'USER',
|
|
32
|
+
'CODEX_API_KEY',
|
|
33
|
+
'OPENAI_API_KEY',
|
|
34
|
+
'OPENAI_BASE_URL',
|
|
35
|
+
];
|
|
36
|
+
for (const k of passthrough) {
|
|
37
|
+
const v = process.env[k];
|
|
38
|
+
if (v !== undefined)
|
|
39
|
+
merged[k] = v;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
for (const [k, v] of Object.entries(opts.env))
|
|
44
|
+
merged[k] = v;
|
|
45
|
+
}
|
|
46
|
+
if (overrides.env !== undefined) {
|
|
47
|
+
for (const [k, v] of Object.entries(overrides.env))
|
|
48
|
+
merged[k] = v;
|
|
49
|
+
}
|
|
50
|
+
out.env = merged;
|
|
51
|
+
}
|
|
52
|
+
if (overrides.config !== undefined) {
|
|
53
|
+
out.config = overrides.config;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/** Build ThreadOptions for `Codex.startThread`. */
|
|
58
|
+
export function buildThreadOptions(opts, extras) {
|
|
59
|
+
const out = {};
|
|
60
|
+
if (opts.model !== undefined)
|
|
61
|
+
out.model = opts.model;
|
|
62
|
+
if (opts.modelReasoningEffort !== undefined)
|
|
63
|
+
out.modelReasoningEffort = opts.modelReasoningEffort;
|
|
64
|
+
if (opts.skipGitRepoCheck !== undefined)
|
|
65
|
+
out.skipGitRepoCheck = opts.skipGitRepoCheck;
|
|
66
|
+
else
|
|
67
|
+
out.skipGitRepoCheck = true;
|
|
68
|
+
Object.assign(out, extras);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Translate a skelm `McpServerConfig` array into the JS shape that the
|
|
73
|
+
* Codex SDK's `config.mcp_servers` accepts. Today Codex's TOML config only
|
|
74
|
+
* supports stdio MCP servers via `command` + `args` + `env`; HTTP/SSE
|
|
75
|
+
* transports are dropped with a warning so the caller can audit the gap.
|
|
76
|
+
*
|
|
77
|
+
* The caller is expected to have already filtered by `policy.allowedMcpServers`.
|
|
78
|
+
*/
|
|
79
|
+
export function buildMcpServerConfig(servers) {
|
|
80
|
+
if (servers.length === 0)
|
|
81
|
+
return null;
|
|
82
|
+
const out = {};
|
|
83
|
+
const dropped = [];
|
|
84
|
+
for (const s of servers) {
|
|
85
|
+
if (s.transport === 'stdio' && s.command !== undefined) {
|
|
86
|
+
const entry = { command: s.command };
|
|
87
|
+
if (s.args !== undefined)
|
|
88
|
+
entry.args = [...s.args];
|
|
89
|
+
if (s.env !== undefined)
|
|
90
|
+
entry.env = { ...s.env };
|
|
91
|
+
out[s.id] = entry;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Codex CLI's stable config.toml schema doesn't expose HTTP/SSE MCP
|
|
95
|
+
// transports; users with remote MCP must wire ~/.codex/config.toml
|
|
96
|
+
// manually. Surface the drop in audit instead of silently swallowing.
|
|
97
|
+
dropped.push(s.id);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (Object.keys(out).length === 0)
|
|
101
|
+
return null;
|
|
102
|
+
return { mcp_servers: out, dropped };
|
|
103
|
+
}
|
|
104
|
+
export async function consumeStream(events, callbacks) {
|
|
105
|
+
let finalText = '';
|
|
106
|
+
let usage = undefined;
|
|
107
|
+
let stopReason = 'turn.completed';
|
|
108
|
+
for await (const ev of events) {
|
|
109
|
+
switch (ev.type) {
|
|
110
|
+
case 'item.completed': {
|
|
111
|
+
// Aggregate assistant message text. `agent_message.text` is the
|
|
112
|
+
// full cumulative text up to this point — `onText` per skelm's
|
|
113
|
+
// BackendContext.onPartial contract should receive *deltas*, so we
|
|
114
|
+
// emit only the new suffix on each event.
|
|
115
|
+
if (ev.item.type === 'agent_message') {
|
|
116
|
+
const next = ev.item.text;
|
|
117
|
+
const delta = next.startsWith(finalText) ? next.slice(finalText.length) : next;
|
|
118
|
+
finalText = next;
|
|
119
|
+
if (delta.length > 0)
|
|
120
|
+
callbacks.onText?.(delta);
|
|
121
|
+
}
|
|
122
|
+
callbacks.onItem?.(ev);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case 'turn.completed':
|
|
126
|
+
usage = {
|
|
127
|
+
inputTokens: ev.usage.input_tokens,
|
|
128
|
+
outputTokens: ev.usage.output_tokens,
|
|
129
|
+
reasoningTokens: ev.usage.reasoning_output_tokens,
|
|
130
|
+
};
|
|
131
|
+
stopReason = 'turn.completed';
|
|
132
|
+
break;
|
|
133
|
+
case 'turn.failed':
|
|
134
|
+
stopReason = 'turn.failed';
|
|
135
|
+
callbacks.onError?.(ev.error.message);
|
|
136
|
+
throw new Error(`codex turn failed: ${ev.error.message}`);
|
|
137
|
+
case 'error':
|
|
138
|
+
stopReason = 'error';
|
|
139
|
+
callbacks.onError?.(ev.message);
|
|
140
|
+
throw new Error(`codex stream error: ${ev.message}`);
|
|
141
|
+
// thread.started, turn.started, item.started, item.updated: not
|
|
142
|
+
// material to the final response; surface via onItem if the caller
|
|
143
|
+
// wants per-item audit (it doesn't, by default).
|
|
144
|
+
default:
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { finalText, usage, stopReason };
|
|
149
|
+
}
|
|
150
|
+
/** Construct a `Codex` instance. Exposed for testing. */
|
|
151
|
+
export function makeCodexClient(opts) {
|
|
152
|
+
return new Codex(opts);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @skelm/codex — OpenAI Codex backend for skelm via `@openai/codex-sdk`.
|
|
3
|
+
*
|
|
4
|
+
* Codex authenticates via the host `codex` CLI (`codex login`) or
|
|
5
|
+
* `CODEX_API_KEY`. The SDK spawns codex under the hood and exchanges JSONL
|
|
6
|
+
* events; skelm enforces permissions at the boundary and records audit
|
|
7
|
+
* events as Codex emits tool calls, file changes, and shell executions.
|
|
8
|
+
*/
|
|
9
|
+
export { createCodexBackend } from './backend.js';
|
|
10
|
+
export { CodexPermissionError, buildAuditEntry, filterIds, mapPermissionsToCodex, } from './permission-mapper.js';
|
|
11
|
+
export type { CodexBackendOptions, CodexPermissionAuditEntry, MappedCodexPolicy, } from './types.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,SAAS,EACT,qBAAqB,GACtB,MAAM,wBAAwB,CAAA;AAC/B,YAAY,EACV,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,GAClB,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @skelm/codex — OpenAI Codex backend for skelm via `@openai/codex-sdk`.
|
|
3
|
+
*
|
|
4
|
+
* Codex authenticates via the host `codex` CLI (`codex login`) or
|
|
5
|
+
* `CODEX_API_KEY`. The SDK spawns codex under the hood and exchanges JSONL
|
|
6
|
+
* events; skelm enforces permissions at the boundary and records audit
|
|
7
|
+
* events as Codex emits tool calls, file changes, and shell executions.
|
|
8
|
+
*/
|
|
9
|
+
export { createCodexBackend } from './backend.js';
|
|
10
|
+
export { CodexPermissionError, buildAuditEntry, filterIds, mapPermissionsToCodex, } from './permission-mapper.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,SAAS,EACT,qBAAqB,GACtB,MAAM,wBAAwB,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate a resolved skelm permission policy into Codex SDK options.
|
|
3
|
+
*
|
|
4
|
+
* Codex enforces its own sandbox in-process; the mapper is the boundary —
|
|
5
|
+
* if it can't translate the policy without widening, it refuses the run.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
*
|
|
9
|
+
* fsWrite: ∅ and fsRead: ∅ → sandboxMode: 'read-only'
|
|
10
|
+
* fsWrite: ['*'] → 'danger-full-access' iff approval.mode === 'never'
|
|
11
|
+
* (otherwise refuse — never silently escalate)
|
|
12
|
+
* fsWrite: [<roots>] → 'workspace-write', primary root → workingDirectory,
|
|
13
|
+
* extras → additionalDirectories
|
|
14
|
+
* networkEgress: 'deny' → networkAccessEnabled: false
|
|
15
|
+
* networkEgress: anything else → networkAccessEnabled: true
|
|
16
|
+
* (host allowlists are enforced by skelm's
|
|
17
|
+
* gateway egress proxy, not by Codex)
|
|
18
|
+
*
|
|
19
|
+
* Approval:
|
|
20
|
+
*
|
|
21
|
+
* approval == null → 'on-request' (Codex's safest default)
|
|
22
|
+
* approval.on.includes('*') → 'untrusted'
|
|
23
|
+
* otherwise → 'on-request'
|
|
24
|
+
*
|
|
25
|
+
* Refusals throw `CodexPermissionError` and are emitted as audit entries.
|
|
26
|
+
*/
|
|
27
|
+
import type { ResolvedPolicy } from '@skelm/core';
|
|
28
|
+
import type { CodexPermissionAuditEntry, MappedCodexPolicy } from './types.js';
|
|
29
|
+
export declare class CodexPermissionError extends Error {
|
|
30
|
+
readonly dimension: 'fs.write' | 'fs.read' | 'network' | 'approval';
|
|
31
|
+
readonly name = "CodexPermissionError";
|
|
32
|
+
constructor(message: string, dimension: 'fs.write' | 'fs.read' | 'network' | 'approval');
|
|
33
|
+
}
|
|
34
|
+
export interface MapInputs {
|
|
35
|
+
/** Resolved policy after profiles + step-level merging. */
|
|
36
|
+
policy: ResolvedPolicy;
|
|
37
|
+
/** Working directory hint from the runtime (`WorkspaceHandle.path`). */
|
|
38
|
+
workingDirectory?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Produce the Codex `ThreadOptions`-compatible mapping. Throws
|
|
42
|
+
* `CodexPermissionError` when the policy cannot be honored safely.
|
|
43
|
+
*/
|
|
44
|
+
export declare function mapPermissionsToCodex(input: MapInputs): MappedCodexPolicy;
|
|
45
|
+
/**
|
|
46
|
+
* Compute which skill / MCP ids the step requested but the resolved policy
|
|
47
|
+
* disallows. Caller fires `permission.denied` events for each.
|
|
48
|
+
*/
|
|
49
|
+
export declare function filterByPolicy<T extends {
|
|
50
|
+
id: string;
|
|
51
|
+
}>(items: readonly T[] | undefined, allowlist: ReadonlySet<string>): {
|
|
52
|
+
allowed: T[];
|
|
53
|
+
denied: T[];
|
|
54
|
+
};
|
|
55
|
+
/** Same as filterByPolicy but for plain string ids (skills, secrets). */
|
|
56
|
+
export declare function filterIds(ids: readonly string[] | undefined, allowlist: ReadonlySet<string>): {
|
|
57
|
+
allowed: string[];
|
|
58
|
+
denied: string[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Build an audit entry recording how a policy was translated. Useful for
|
|
62
|
+
* the gateway's hash-chained audit writer.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildAuditEntry(runId: string, stepId: string, policy: ResolvedPolicy, mapped: MappedCodexPolicy, denied: readonly string[]): CodexPermissionAuditEntry;
|
|
65
|
+
//# sourceMappingURL=permission-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-mapper.d.ts","sourceRoot":"","sources":["../src/permission-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAI9E,qBAAa,oBAAqB,SAAQ,KAAK;IAI3C,QAAQ,CAAC,SAAS,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU;IAHrE,SAAkB,IAAI,0BAAyB;gBAE7C,OAAO,EAAE,MAAM,EACN,SAAS,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU;CAItE;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,MAAM,EAAE,cAAc,CAAA;IACtB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,iBAAiB,CAiEzE;AAkBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EACrD,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,SAAS,EAC/B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,MAAM,EAAE,CAAC,EAAE,CAAA;CAAE,CAS/B;AAED,yEAAyE;AACzE,wBAAgB,SAAS,CACvB,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAClC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,GAC7B;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CASzC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,yBAAyB,CAwB3B"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate a resolved skelm permission policy into Codex SDK options.
|
|
3
|
+
*
|
|
4
|
+
* Codex enforces its own sandbox in-process; the mapper is the boundary —
|
|
5
|
+
* if it can't translate the policy without widening, it refuses the run.
|
|
6
|
+
*
|
|
7
|
+
* Rules:
|
|
8
|
+
*
|
|
9
|
+
* fsWrite: ∅ and fsRead: ∅ → sandboxMode: 'read-only'
|
|
10
|
+
* fsWrite: ['*'] → 'danger-full-access' iff approval.mode === 'never'
|
|
11
|
+
* (otherwise refuse — never silently escalate)
|
|
12
|
+
* fsWrite: [<roots>] → 'workspace-write', primary root → workingDirectory,
|
|
13
|
+
* extras → additionalDirectories
|
|
14
|
+
* networkEgress: 'deny' → networkAccessEnabled: false
|
|
15
|
+
* networkEgress: anything else → networkAccessEnabled: true
|
|
16
|
+
* (host allowlists are enforced by skelm's
|
|
17
|
+
* gateway egress proxy, not by Codex)
|
|
18
|
+
*
|
|
19
|
+
* Approval:
|
|
20
|
+
*
|
|
21
|
+
* approval == null → 'on-request' (Codex's safest default)
|
|
22
|
+
* approval.on.includes('*') → 'untrusted'
|
|
23
|
+
* otherwise → 'on-request'
|
|
24
|
+
*
|
|
25
|
+
* Refusals throw `CodexPermissionError` and are emitted as audit entries.
|
|
26
|
+
*/
|
|
27
|
+
const STAR = '*';
|
|
28
|
+
export class CodexPermissionError extends Error {
|
|
29
|
+
dimension;
|
|
30
|
+
name = 'CodexPermissionError';
|
|
31
|
+
constructor(message, dimension) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.dimension = dimension;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Produce the Codex `ThreadOptions`-compatible mapping. Throws
|
|
38
|
+
* `CodexPermissionError` when the policy cannot be honored safely.
|
|
39
|
+
*/
|
|
40
|
+
export function mapPermissionsToCodex(input) {
|
|
41
|
+
const { policy, workingDirectory } = input;
|
|
42
|
+
const fsWrite = Array.from(policy.fsWrite);
|
|
43
|
+
const fsRead = Array.from(policy.fsRead);
|
|
44
|
+
// Sandbox tier.
|
|
45
|
+
let sandboxMode;
|
|
46
|
+
let primary = workingDirectory;
|
|
47
|
+
const extras = [];
|
|
48
|
+
const hasStarWrite = fsWrite.includes(STAR);
|
|
49
|
+
if (hasStarWrite) {
|
|
50
|
+
// Broad write requires explicit no-approval acknowledgement.
|
|
51
|
+
if (policy.approval !== null) {
|
|
52
|
+
throw new CodexPermissionError('fsWrite includes "*" but an approval policy is set; refusing danger-full-access', 'fs.write');
|
|
53
|
+
}
|
|
54
|
+
sandboxMode = 'danger-full-access';
|
|
55
|
+
}
|
|
56
|
+
else if (fsWrite.length === 0) {
|
|
57
|
+
sandboxMode = 'read-only';
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
sandboxMode = 'workspace-write';
|
|
61
|
+
if (primary === undefined)
|
|
62
|
+
primary = fsWrite[0];
|
|
63
|
+
for (const root of fsWrite) {
|
|
64
|
+
if (root !== primary && !extras.includes(root))
|
|
65
|
+
extras.push(root);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Read-only sandbox doesn't need fsRead roots (codex's read-only mode is
|
|
69
|
+
// unrestricted by default; the read allowlist is informational here).
|
|
70
|
+
void fsRead;
|
|
71
|
+
// Network. Codex has two distinct network surfaces:
|
|
72
|
+
// 1. `networkAccessEnabled` — sandbox-shell egress (curl, wget, etc).
|
|
73
|
+
// The gateway egress proxy can enforce host allowlists for this
|
|
74
|
+
// surface when `networkEgress: { allowHosts }` is set.
|
|
75
|
+
// 2. `webSearchEnabled` / `webSearchMode` — the model's built-in web
|
|
76
|
+
// tool. This runs INSIDE the Codex process and does NOT go through
|
|
77
|
+
// the gateway proxy, so per-host allowlists cannot be enforced.
|
|
78
|
+
//
|
|
79
|
+
// Implication: a `{ allowHosts: ['api.example.com'] }` policy would, if
|
|
80
|
+
// we left web_search enabled, let Codex reach arbitrary URLs through its
|
|
81
|
+
// built-in tool — bypassing the allowlist the user just declared. To
|
|
82
|
+
// honor the policy faithfully, web_search is enabled ONLY when
|
|
83
|
+
// networkEgress === 'allow' (blanket allow). Anything else disables it.
|
|
84
|
+
const networkAccessEnabled = policy.networkEgress !== 'deny';
|
|
85
|
+
const webSearchAllowed = policy.networkEgress === 'allow';
|
|
86
|
+
const webSearchEnabled = webSearchAllowed;
|
|
87
|
+
const webSearchMode = webSearchAllowed ? 'live' : 'disabled';
|
|
88
|
+
// Approval mode.
|
|
89
|
+
const approvalPolicy = pickApprovalMode(policy);
|
|
90
|
+
const mapped = {
|
|
91
|
+
sandboxMode,
|
|
92
|
+
approvalPolicy,
|
|
93
|
+
networkAccessEnabled,
|
|
94
|
+
webSearchEnabled,
|
|
95
|
+
webSearchMode,
|
|
96
|
+
...(primary !== undefined && { workingDirectory: primary }),
|
|
97
|
+
...(extras.length > 0 && { additionalDirectories: extras }),
|
|
98
|
+
};
|
|
99
|
+
return mapped;
|
|
100
|
+
}
|
|
101
|
+
function pickApprovalMode(policy) {
|
|
102
|
+
// No explicit approval policy → don't escalate. Codex's sandbox is already
|
|
103
|
+
// enforcing what's allowed (sandboxMode + workingDirectory + network); the
|
|
104
|
+
// skelm boundary check has already refused unsafe combinations before we
|
|
105
|
+
// got here. Using 'on-request' here would deadlock every shell command in
|
|
106
|
+
// automated environments because skelm doesn't supply a Codex-side
|
|
107
|
+
// approval handler. Per project tenet "default-deny is structural": the
|
|
108
|
+
// sandbox is the deny, not approval prompts.
|
|
109
|
+
if (policy.approval === null)
|
|
110
|
+
return 'never';
|
|
111
|
+
// Explicit approval policy from the workflow author → respect it.
|
|
112
|
+
if (policy.approval.on.some((d) => d === 'tool' || d === 'executable')) {
|
|
113
|
+
return 'untrusted';
|
|
114
|
+
}
|
|
115
|
+
return 'on-request';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Compute which skill / MCP ids the step requested but the resolved policy
|
|
119
|
+
* disallows. Caller fires `permission.denied` events for each.
|
|
120
|
+
*/
|
|
121
|
+
export function filterByPolicy(items, allowlist) {
|
|
122
|
+
if (items === undefined)
|
|
123
|
+
return { allowed: [], denied: [] };
|
|
124
|
+
const allowed = [];
|
|
125
|
+
const denied = [];
|
|
126
|
+
for (const item of items) {
|
|
127
|
+
if (allowlist.has(item.id))
|
|
128
|
+
allowed.push(item);
|
|
129
|
+
else
|
|
130
|
+
denied.push(item);
|
|
131
|
+
}
|
|
132
|
+
return { allowed, denied };
|
|
133
|
+
}
|
|
134
|
+
/** Same as filterByPolicy but for plain string ids (skills, secrets). */
|
|
135
|
+
export function filterIds(ids, allowlist) {
|
|
136
|
+
if (ids === undefined)
|
|
137
|
+
return { allowed: [], denied: [] };
|
|
138
|
+
const allowed = [];
|
|
139
|
+
const denied = [];
|
|
140
|
+
for (const id of ids) {
|
|
141
|
+
if (allowlist.has(id))
|
|
142
|
+
allowed.push(id);
|
|
143
|
+
else
|
|
144
|
+
denied.push(id);
|
|
145
|
+
}
|
|
146
|
+
return { allowed, denied };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build an audit entry recording how a policy was translated. Useful for
|
|
150
|
+
* the gateway's hash-chained audit writer.
|
|
151
|
+
*/
|
|
152
|
+
export function buildAuditEntry(runId, stepId, policy, mapped, denied) {
|
|
153
|
+
return {
|
|
154
|
+
runId,
|
|
155
|
+
stepId,
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
event: 'permission_check',
|
|
158
|
+
details: {
|
|
159
|
+
declaredPermissions: {
|
|
160
|
+
allowedExecutables: Array.from(policy.allowedExecutables),
|
|
161
|
+
allowedMcpServers: Array.from(policy.allowedMcpServers),
|
|
162
|
+
allowedSkills: Array.from(policy.allowedSkills),
|
|
163
|
+
fsRead: Array.from(policy.fsRead),
|
|
164
|
+
fsWrite: Array.from(policy.fsWrite),
|
|
165
|
+
networkEgress: typeof policy.networkEgress === 'string'
|
|
166
|
+
? policy.networkEgress
|
|
167
|
+
: `allowHosts:${policy.networkEgress.allowHosts.join(',')}`,
|
|
168
|
+
},
|
|
169
|
+
mapped,
|
|
170
|
+
decision: denied.length === 0 ? 'allow' : 'deny',
|
|
171
|
+
deniedItems: Array.from(denied),
|
|
172
|
+
backend: 'codex',
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=permission-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-mapper.js","sourceRoot":"","sources":["../src/permission-mapper.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@skelm/codex` configuration types.
|
|
3
|
+
*
|
|
4
|
+
* The backend wraps OpenAI's official `@openai/codex-sdk`. The SDK spawns
|
|
5
|
+
* the `codex` CLI under the hood and exchanges JSONL events over stdio, so
|
|
6
|
+
* authentication piggybacks on `codex login` (`~/.codex/auth.json`) or the
|
|
7
|
+
* `CODEX_API_KEY` env var.
|
|
8
|
+
*/
|
|
9
|
+
import type { ApprovalMode, ModelReasoningEffort, SandboxMode, WebSearchMode } from '@openai/codex-sdk';
|
|
10
|
+
export interface CodexBackendOptions {
|
|
11
|
+
/** Backend id. Defaults to 'codex' when only one codex backend is registered. */
|
|
12
|
+
id?: string;
|
|
13
|
+
/** Human-readable label for diagnostics. */
|
|
14
|
+
label?: string;
|
|
15
|
+
/** Path to the codex CLI. Default: 'codex' on PATH (the SDK resolves it). */
|
|
16
|
+
codexPathOverride?: string;
|
|
17
|
+
/** Override the Codex API base URL (self-hosted / proxied deployments). */
|
|
18
|
+
baseUrl?: string;
|
|
19
|
+
/** API key. Overrides the ambient `CODEX_API_KEY` env var. */
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Extra env vars passed to the spawned codex process. When set, the SDK
|
|
23
|
+
* does NOT inherit `process.env` — it uses these vars as the full env.
|
|
24
|
+
* Skelm always merges `BackendContext.proxyEnv` into this map at run time.
|
|
25
|
+
*/
|
|
26
|
+
env?: Record<string, string>;
|
|
27
|
+
/** Default model id, e.g. 'gpt-5.2' or 'gpt-5.3-codex'. */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** Default reasoning effort. */
|
|
30
|
+
modelReasoningEffort?: ModelReasoningEffort;
|
|
31
|
+
/**
|
|
32
|
+
* Skip the SDK's "is this a git repo" sanity check. Defaults to `true` for
|
|
33
|
+
* ephemeral skelm workspaces (which usually have no `.git`).
|
|
34
|
+
*/
|
|
35
|
+
skipGitRepoCheck?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Per-step abort timeout. The runtime's own abort signal already drives
|
|
38
|
+
* cancellation; this is a defensive ceiling. Default: 300_000 (5 min).
|
|
39
|
+
*/
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolved Codex SDK options after skelm permissions are applied. Produced
|
|
44
|
+
* by `permission-mapper.ts` and fed into `Codex.startThread()`.
|
|
45
|
+
*/
|
|
46
|
+
export interface MappedCodexPolicy {
|
|
47
|
+
sandboxMode: SandboxMode;
|
|
48
|
+
approvalPolicy: ApprovalMode;
|
|
49
|
+
networkAccessEnabled: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Codex's built-in web search bypasses `networkAccessEnabled` (that flag
|
|
52
|
+
* governs sandbox-shell egress only). When networkEgress is 'deny' we set
|
|
53
|
+
* webSearchMode to 'disabled' too so the agent cannot reach the public
|
|
54
|
+
* web through its built-in tool.
|
|
55
|
+
*/
|
|
56
|
+
webSearchMode: WebSearchMode;
|
|
57
|
+
webSearchEnabled: boolean;
|
|
58
|
+
/** Primary working directory (== WorkspaceHandle.path when present). */
|
|
59
|
+
workingDirectory?: string;
|
|
60
|
+
/** Extra writable directories beyond `workingDirectory`. */
|
|
61
|
+
additionalDirectories?: string[];
|
|
62
|
+
}
|
|
63
|
+
export interface CodexPermissionAuditEntry {
|
|
64
|
+
runId: string;
|
|
65
|
+
stepId: string;
|
|
66
|
+
timestamp: string;
|
|
67
|
+
event: 'permission_check';
|
|
68
|
+
details: {
|
|
69
|
+
declaredPermissions: {
|
|
70
|
+
allowedExecutables: string[];
|
|
71
|
+
allowedMcpServers: string[];
|
|
72
|
+
allowedSkills: string[];
|
|
73
|
+
fsRead: string[];
|
|
74
|
+
fsWrite: string[];
|
|
75
|
+
networkEgress: string;
|
|
76
|
+
};
|
|
77
|
+
mapped: MappedCodexPolicy;
|
|
78
|
+
decision: 'allow' | 'deny';
|
|
79
|
+
deniedItems: string[];
|
|
80
|
+
backend: 'codex';
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACpB,WAAW,EACX,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,MAAM,WAAW,mBAAmB;IAClC,iFAAiF;IACjF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IAId,6EAA6E;IAC7E,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAI5B,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gCAAgC;IAChC,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAI3C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,WAAW,CAAA;IACxB,cAAc,EAAE,YAAY,CAAA;IAC5B,oBAAoB,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAA;IAC5B,gBAAgB,EAAE,OAAO,CAAA;IACzB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,4DAA4D;IAC5D,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAA;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,kBAAkB,CAAA;IACzB,OAAO,EAAE;QACP,mBAAmB,EAAE;YACnB,kBAAkB,EAAE,MAAM,EAAE,CAAA;YAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAA;YAC3B,aAAa,EAAE,MAAM,EAAE,CAAA;YACvB,MAAM,EAAE,MAAM,EAAE,CAAA;YAChB,OAAO,EAAE,MAAM,EAAE,CAAA;YACjB,aAAa,EAAE,MAAM,CAAA;SACtB,CAAA;QACD,MAAM,EAAE,iBAAiB,CAAA;QACzB,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;QAC1B,WAAW,EAAE,MAAM,EAAE,CAAA;QACrB,OAAO,EAAE,OAAO,CAAA;KACjB,CAAA;CACF"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@skelm/codex` configuration types.
|
|
3
|
+
*
|
|
4
|
+
* The backend wraps OpenAI's official `@openai/codex-sdk`. The SDK spawns
|
|
5
|
+
* the `codex` CLI under the hood and exchanges JSONL events over stdio, so
|
|
6
|
+
* authentication piggybacks on `codex login` (`~/.codex/auth.json`) or the
|
|
7
|
+
* `CODEX_API_KEY` env var.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skelm/codex",
|
|
3
|
+
"version": "0.3.9",
|
|
4
|
+
"description": "OpenAI Codex backend for skelm via the official @openai/codex-sdk",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Scott Glover <scottgl@gmail.com>",
|
|
7
|
+
"homepage": "https://skelm.dev/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/scottgl9/skelm.git",
|
|
11
|
+
"directory": "packages/codex"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/scottgl9/skelm/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"skelm",
|
|
18
|
+
"codex",
|
|
19
|
+
"openai",
|
|
20
|
+
"agent",
|
|
21
|
+
"backend",
|
|
22
|
+
"coding-agent"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"registry": "https://registry.npmjs.org/"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.json",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@openai/codex-sdk": "^0.130.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@skelm/core": "^0.3.9"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@skelm/core": "^0.3.9",
|
|
56
|
+
"@types/node": "^20.10.0",
|
|
57
|
+
"typescript": "^5.3.0",
|
|
58
|
+
"vitest": "^2.1.5"
|
|
59
|
+
}
|
|
60
|
+
}
|