codiedev 0.10.0-beta.16 → 0.10.0-beta.17
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/dist/cli.js +63 -18
- package/dist/codie.js +19 -2
- package/dist/codieLauncher.js +20 -1
- package/dist/commands/agents.js +18 -1
- package/dist/commands/agentsSdk.generated.js +3 -3
- package/dist/commands/inbox.js +9 -0
- package/dist/commands/pack.js +258 -12
- package/dist/commands/ping.js +35 -10
- package/dist/commands/pingFiles.js +145 -0
- package/dist/commands/push.js +33 -4
- package/dist/commands/reply.js +49 -0
- package/dist/commands/scrub.js +1 -2
- package/dist/commands/send.js +11 -51
- package/dist/commands/shared.js +51 -0
- package/dist/commands/work.js +761 -0
- package/dist/connect.js +3 -0
- package/dist/hook.js +50 -5
- package/dist/inboxFormat.js +17 -1
- package/dist/launchContext.js +56 -0
- package/dist/launcher.js +24 -1
- package/dist/managedPi.js +1 -0
- package/dist/mcp.js +15 -77
- package/dist/packs/files.js +291 -0
- package/dist/packs/harnesses.js +2 -3
- package/dist/packs/install.js +31 -3
- package/dist/packs/localSkills.js +120 -0
- package/dist/packs/manifest.js +51 -2
- package/dist/packs/skillFiles.js +106 -0
- package/dist/packs/store.js +21 -3
- package/dist/promptGuard.js +223 -0
- package/dist/scrub/rules.js +260 -21
- package/dist/teamSkillContext.js +1 -1
- package/dist/unreadNotice.js +72 -0
- package/dist/utils.js +120 -25
- package/package.json +2 -2
- package/dist/commands/ask.js +0 -55
package/dist/cli.js
CHANGED
|
@@ -40,10 +40,11 @@ const utils_1 = require("./utils");
|
|
|
40
40
|
const push_1 = require("./commands/push");
|
|
41
41
|
const delete_1 = require("./commands/delete");
|
|
42
42
|
const pull_1 = require("./commands/pull");
|
|
43
|
-
const ask_1 = require("./commands/ask");
|
|
44
43
|
const ping_1 = require("./commands/ping");
|
|
44
|
+
const reply_1 = require("./commands/reply");
|
|
45
45
|
const inbox_1 = require("./commands/inbox");
|
|
46
46
|
const note_1 = require("./commands/note");
|
|
47
|
+
const work_1 = require("./commands/work");
|
|
47
48
|
const doctor_1 = require("./commands/doctor");
|
|
48
49
|
const search_1 = require("./commands/search");
|
|
49
50
|
const library_1 = require("./commands/library");
|
|
@@ -85,19 +86,16 @@ Artifacts:
|
|
|
85
86
|
codiedev delete <key> [-y] Delete every version of an artifact you
|
|
86
87
|
authored (clean up bad/test pushes).
|
|
87
88
|
Asks for confirmation; -y to skip.
|
|
88
|
-
codiedev ask "<question>" Ask a natural-language question of the
|
|
89
|
-
team's collective memory. Returns an
|
|
90
|
-
LLM-synthesized answer with citations
|
|
91
|
-
pointing at sessions + artifacts.
|
|
92
89
|
|
|
93
90
|
Messaging:
|
|
94
|
-
codiedev ping <user> "<msg>" [--with <key>]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Silently grant access (no notification)
|
|
91
|
+
codiedev ping <user> ["<msg>"] [--with <key>] [--edit] [--file <path>]
|
|
92
|
+
Message a teammate (emails them). --with
|
|
93
|
+
gives them access to an artifact; with
|
|
94
|
+
no message it's a silent share
|
|
99
95
|
codiedev inbox [--unread] [--limit N] Show messages
|
|
100
96
|
codiedev read <ping-id> Mark read + show full message
|
|
97
|
+
codiedev reply <ping-id> "<msg>" [--file <path>]
|
|
98
|
+
Answer a message in its thread
|
|
101
99
|
|
|
102
100
|
Starter packs:
|
|
103
101
|
codiedev pack list Packs assigned to your team
|
|
@@ -138,6 +136,9 @@ Privacy:
|
|
|
138
136
|
|
|
139
137
|
Capture:
|
|
140
138
|
codiedev note "<text>" Capture a passing thought
|
|
139
|
+
codiedev project create "<name>" Start a project (Break Room)
|
|
140
|
+
codiedev task add <project> "<title>" Add a task; also start, done, next, ask
|
|
141
|
+
codiedev webhooks add <url> Get task/ping events at your own URL
|
|
141
142
|
|
|
142
143
|
Other:
|
|
143
144
|
codiedev help [command] Show this help, or details for one command
|
|
@@ -266,19 +267,43 @@ codiedev pull — fetch an artifact
|
|
|
266
267
|
codiedev pull spec-cart-clear.md | less
|
|
267
268
|
`.trim(),
|
|
268
269
|
ping: `
|
|
269
|
-
codiedev ping — send a teammate a message,
|
|
270
|
+
codiedev ping — send a teammate a message, an artifact, or both
|
|
270
271
|
|
|
271
272
|
Usage:
|
|
272
|
-
codiedev ping <user> "<message>" [--with <artifact-key>]
|
|
273
|
+
codiedev ping <user> ["<message>"] [--with <artifact-key>] [--edit] [--file <path>]
|
|
274
|
+
|
|
275
|
+
--with gives them read access to the artifact (--edit: edit access; only
|
|
276
|
+
the owner can give edit access or share a private skill). With no message,
|
|
277
|
+
it's a silent share: access only, no inbox entry, no email.
|
|
273
278
|
|
|
274
279
|
<user> resolves to a teammate in your org by first name, full name,
|
|
275
280
|
or email. Ambiguous matches return a list — retry with the full email.
|
|
276
281
|
|
|
282
|
+
--file attaches a file (repeat for up to 10, 20 MB each). They see it
|
|
283
|
+
in their inbox and can open it in the portal.
|
|
284
|
+
|
|
277
285
|
Teammates are notified by email and can reply from their own agent.
|
|
286
|
+
To answer a message you received, use 'codiedev reply' instead: it
|
|
287
|
+
keeps the exchange in one thread.
|
|
278
288
|
|
|
279
289
|
Examples:
|
|
280
290
|
codiedev ping <teammate> "thoughts on batch vs parallel?" --with spec-cart-clear.md
|
|
281
291
|
codiedev ping <teammate>@example.com "can you take this one?"
|
|
292
|
+
codiedev ping <teammate> "shard timings from last night" --file ./timings.csv
|
|
293
|
+
`.trim(),
|
|
294
|
+
reply: `
|
|
295
|
+
codiedev reply — answer a message in its thread
|
|
296
|
+
|
|
297
|
+
Usage:
|
|
298
|
+
codiedev reply <ping-id> "<message>" [--file <path>]
|
|
299
|
+
|
|
300
|
+
Grab the ping-id from 'codiedev inbox'. The reply goes to whoever sent
|
|
301
|
+
that ping, stays in the same conversation, and marks their message
|
|
302
|
+
read and answered, so it stops showing as waiting on you.
|
|
303
|
+
|
|
304
|
+
Examples:
|
|
305
|
+
codiedev reply <ping-id> "yes, step 2 covers partial refunds"
|
|
306
|
+
codiedev reply <ping-id> "updated numbers attached" --file ./timings.csv
|
|
282
307
|
`.trim(),
|
|
283
308
|
inbox: `
|
|
284
309
|
codiedev inbox — show messages from teammates
|
|
@@ -310,6 +335,10 @@ codiedev read — mark a ping as read and show its full content
|
|
|
310
335
|
drop out of 'codiedev inbox --unread' and the portal inbox badge
|
|
311
336
|
decrements for you.
|
|
312
337
|
`.trim(),
|
|
338
|
+
project: work_1.PROJECT_HELP,
|
|
339
|
+
task: work_1.ISSUE_HELP,
|
|
340
|
+
issue: work_1.ISSUE_HELP,
|
|
341
|
+
webhooks: work_1.WEBHOOKS_HELP,
|
|
313
342
|
note: `
|
|
314
343
|
codiedev note — capture a passing thought, linked to your session
|
|
315
344
|
|
|
@@ -400,13 +429,12 @@ signal. The stored artifact type still follows the filename rule
|
|
|
400
429
|
|
|
401
430
|
## Portal
|
|
402
431
|
|
|
403
|
-
|
|
432
|
+
Messages you send and receive are also at:
|
|
404
433
|
|
|
405
434
|
https://codiedev.com/portal/inbox
|
|
406
435
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
email notifications when pinged.
|
|
436
|
+
A teammate can read and answer there without touching a terminal, and
|
|
437
|
+
gets an email when pinged.
|
|
410
438
|
|
|
411
439
|
## Agent-native
|
|
412
440
|
|
|
@@ -536,8 +564,8 @@ async function main() {
|
|
|
536
564
|
await (0, pull_1.runPull)(rest);
|
|
537
565
|
return;
|
|
538
566
|
case "ask":
|
|
539
|
-
|
|
540
|
-
|
|
567
|
+
console.error('codiedev ask has been removed. Use: codiedev search "<query>"');
|
|
568
|
+
process.exit(1);
|
|
541
569
|
case "ping":
|
|
542
570
|
await (0, ping_1.runPing)(rest);
|
|
543
571
|
return;
|
|
@@ -553,6 +581,9 @@ async function main() {
|
|
|
553
581
|
case "read":
|
|
554
582
|
await (0, inbox_1.runRead)(rest);
|
|
555
583
|
return;
|
|
584
|
+
case "reply":
|
|
585
|
+
await (0, reply_1.runReply)(rest);
|
|
586
|
+
return;
|
|
556
587
|
case "scrub": {
|
|
557
588
|
const { runScrubCommand } = await Promise.resolve().then(() => __importStar(require("./commands/scrub")));
|
|
558
589
|
process.exitCode = await runScrubCommand(rest);
|
|
@@ -561,6 +592,20 @@ async function main() {
|
|
|
561
592
|
case "note":
|
|
562
593
|
await (0, note_1.runNote)(rest);
|
|
563
594
|
return;
|
|
595
|
+
case "project":
|
|
596
|
+
case "projects":
|
|
597
|
+
await (0, work_1.runProject)(rest);
|
|
598
|
+
return;
|
|
599
|
+
case "task":
|
|
600
|
+
case "tasks":
|
|
601
|
+
case "issue":
|
|
602
|
+
case "issues":
|
|
603
|
+
await (0, work_1.runIssue)(rest);
|
|
604
|
+
return;
|
|
605
|
+
case "webhooks":
|
|
606
|
+
case "webhook":
|
|
607
|
+
await (0, work_1.runWebhooks)(rest);
|
|
608
|
+
return;
|
|
564
609
|
case "promote":
|
|
565
610
|
case "reverse-ticket":
|
|
566
611
|
case "reverseTicket":
|
package/dist/codie.js
CHANGED
|
@@ -40,6 +40,7 @@ const path = __importStar(require("path"));
|
|
|
40
40
|
const child_process_1 = require("child_process");
|
|
41
41
|
const url_1 = require("url");
|
|
42
42
|
const configDir_1 = require("./configDir");
|
|
43
|
+
const utils_1 = require("./utils");
|
|
43
44
|
const codieLauncher_1 = require("./codieLauncher");
|
|
44
45
|
const codieBrandInstall_1 = require("./codieBrandInstall");
|
|
45
46
|
const codieUsage_1 = require("./codieUsage");
|
|
@@ -169,7 +170,12 @@ async function runNonInteractive(runtime, runtimeArgs, env) {
|
|
|
169
170
|
process.once("SIGTERM", forwardTermination);
|
|
170
171
|
return await new Promise((resolve, reject) => {
|
|
171
172
|
child.stdout?.on("data", (chunk) => process.stdout.write(chunk));
|
|
172
|
-
child.stderr?.on("data", (chunk) =>
|
|
173
|
+
child.stderr?.on("data", (chunk) => {
|
|
174
|
+
const text = chunk.toString("utf8");
|
|
175
|
+
if (!text.includes(codieLauncher_1.PROMPT_BLOCK_MARKER))
|
|
176
|
+
return process.stderr.write(chunk);
|
|
177
|
+
process.stderr.write(text.split(/\r?\n/).map((line) => (0, codieLauncher_1.promptBlockLine)(line) ?? line).join("\n"));
|
|
178
|
+
});
|
|
173
179
|
child.once("error", (error) => {
|
|
174
180
|
if (child.stdin)
|
|
175
181
|
process.stdin.unpipe(child.stdin);
|
|
@@ -290,12 +296,21 @@ async function main() {
|
|
|
290
296
|
const capturePlugin = (0, codieLauncher_1.resolveCapturePlugin)({ homeDir: os.homedir(), existsSync: fs.existsSync });
|
|
291
297
|
(0, codieBrandInstall_1.installTuiPlugin)();
|
|
292
298
|
installBrandWordmark(runtime);
|
|
293
|
-
const
|
|
299
|
+
const instructionsFile = path.join(stateDir, "codie-instructions.md");
|
|
300
|
+
try {
|
|
301
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
302
|
+
fs.writeFileSync(instructionsFile, (0, utils_1.codiedevInstructionsText)(), "utf8");
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
console.error(`codie: couldn't write the Codiedev instructions (${error instanceof Error ? error.message : error})`);
|
|
306
|
+
}
|
|
307
|
+
const baseConfig = (0, codieLauncher_1.buildCodieConfig)({
|
|
294
308
|
existing: withStarterPacks(existingInlineConfig(), new Set(catalog.map((model) => (0, codieLauncher_1.bareModel)(model.id)).filter(Boolean)), catalog),
|
|
295
309
|
catalog,
|
|
296
310
|
env: { ...process.env, ...parsed.overrides, CODIEDEV_GATEWAY_URL: gatewayUrl },
|
|
297
311
|
plugins: [capturePlugin, metadataPlugin],
|
|
298
312
|
});
|
|
313
|
+
const config = fs.existsSync(instructionsFile) ? (0, codieLauncher_1.withCodiedevInstructions)(baseConfig, instructionsFile) : baseConfig;
|
|
299
314
|
const env = {
|
|
300
315
|
...process.env,
|
|
301
316
|
...parsed.overrides,
|
|
@@ -307,6 +322,8 @@ async function main() {
|
|
|
307
322
|
};
|
|
308
323
|
const runtimeArgs = (0, codieLauncher_1.withCodieWorkingDirectory)(parsed.args, process.cwd());
|
|
309
324
|
const nonInteractiveRun = runtimeArgs[0] === "run";
|
|
325
|
+
if (nonInteractiveRun)
|
|
326
|
+
env.CODIEDEV_PROMPT_BLOCK_OUTPUT = "marker";
|
|
310
327
|
if (nonInteractiveRun) {
|
|
311
328
|
const reportCost = Boolean(process.stderr.isTTY);
|
|
312
329
|
const startedAt = Date.now();
|
package/dist/codieLauncher.js
CHANGED
|
@@ -33,11 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = exports.USAGE_MODULE_PLACEHOLDER = void 0;
|
|
36
|
+
exports.PROMPT_BLOCK_MARKER = exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = exports.USAGE_MODULE_PLACEHOLDER = void 0;
|
|
37
37
|
exports.usableModelCost = usableModelCost;
|
|
38
38
|
exports.usableModelLimit = usableModelLimit;
|
|
39
39
|
exports.withCodieWorkingDirectory = withCodieWorkingDirectory;
|
|
40
40
|
exports.bareModel = bareModel;
|
|
41
|
+
exports.withCodiedevInstructions = withCodiedevInstructions;
|
|
41
42
|
exports.buildCodieConfig = buildCodieConfig;
|
|
42
43
|
exports.stageUsageModule = stageUsageModule;
|
|
43
44
|
exports.withBrandPlugin = withBrandPlugin;
|
|
@@ -46,6 +47,7 @@ exports.resolveCapturePlugin = resolveCapturePlugin;
|
|
|
46
47
|
exports.fetchApprovedCatalog = fetchApprovedCatalog;
|
|
47
48
|
exports.parseCodieArgs = parseCodieArgs;
|
|
48
49
|
exports.validateCodieArgs = validateCodieArgs;
|
|
50
|
+
exports.promptBlockLine = promptBlockLine;
|
|
49
51
|
const path = __importStar(require("path"));
|
|
50
52
|
const url_1 = require("url");
|
|
51
53
|
const harnessDirs_1 = require("./harnessDirs");
|
|
@@ -117,6 +119,10 @@ function normalizeAgentModels(agents, approved, fallback) {
|
|
|
117
119
|
}
|
|
118
120
|
return normalized;
|
|
119
121
|
}
|
|
122
|
+
function withCodiedevInstructions(config, file) {
|
|
123
|
+
const current = Array.isArray(config.instructions) ? config.instructions.filter((x) => typeof x === "string") : [];
|
|
124
|
+
return { ...config, instructions: current.includes(file) ? current : [...current, file] };
|
|
125
|
+
}
|
|
120
126
|
function buildCodieConfig({ existing = {}, catalog, env = {}, plugin, plugins = [], }) {
|
|
121
127
|
if (!Array.isArray(catalog) || catalog.length === 0) {
|
|
122
128
|
throw new Error("No company-approved models were returned by Codiedev");
|
|
@@ -382,3 +388,16 @@ function validateCodieArgs(args, catalog) {
|
|
|
382
388
|
}
|
|
383
389
|
}
|
|
384
390
|
}
|
|
391
|
+
exports.PROMPT_BLOCK_MARKER = "__CODIEDEV_PROMPT_BLOCKED__";
|
|
392
|
+
function promptBlockLine(line) {
|
|
393
|
+
const at = line.indexOf(exports.PROMPT_BLOCK_MARKER);
|
|
394
|
+
if (at < 0)
|
|
395
|
+
return null;
|
|
396
|
+
try {
|
|
397
|
+
const reason = JSON.parse(line.slice(at + exports.PROMPT_BLOCK_MARKER.length)).reason;
|
|
398
|
+
return typeof reason === "string" && reason.trim() ? `\n${reason.trim()}` : null;
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
package/dist/commands/agents.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AGENT_GUIDE = exports.AGENTS_HELP = void 0;
|
|
37
37
|
exports.parseEvery = parseEvery;
|
|
38
38
|
exports.parseDuration = parseDuration;
|
|
39
|
+
exports.companyDefaultModel = companyDefaultModel;
|
|
39
40
|
exports.runAgents = runAgents;
|
|
40
41
|
const fs = __importStar(require("fs"));
|
|
41
42
|
const path = __importStar(require("path"));
|
|
@@ -910,6 +911,20 @@ function readEnvFile(file) {
|
|
|
910
911
|
}
|
|
911
912
|
return out;
|
|
912
913
|
}
|
|
914
|
+
async function companyDefaultModel(token, gatewayUrl) {
|
|
915
|
+
const base = (gatewayUrl || process.env.CODIEDEV_GATEWAY_URL || "https://gateway.codiedev.com/api/v1").replace(/\/$/, "");
|
|
916
|
+
try {
|
|
917
|
+
const res = await fetch(`${base}/models`, { headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5000) });
|
|
918
|
+
if (!res.ok)
|
|
919
|
+
return null;
|
|
920
|
+
const body = (await res.json());
|
|
921
|
+
const first = body.data?.[0]?.id;
|
|
922
|
+
return typeof first === "string" && first ? first : null;
|
|
923
|
+
}
|
|
924
|
+
catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
913
928
|
async function cmdRunLocal(args) {
|
|
914
929
|
const dir = path.resolve(positional(args)[0] ?? ".");
|
|
915
930
|
const meta = readAgentJson(dir);
|
|
@@ -950,6 +965,7 @@ async function cmdRunLocal(args) {
|
|
|
950
965
|
if (remoteState && !meta.name)
|
|
951
966
|
fail('--remote-state needs "name" in agent.json, so it uses the right agent\'s state.');
|
|
952
967
|
const runId = `local-${Date.now()}`;
|
|
968
|
+
const model = secrets.CODIEDEV_MODEL || process.env.CODIEDEV_MODEL || (config ? await companyDefaultModel(config.token, secrets.CODIEDEV_GATEWAY_URL) : null);
|
|
953
969
|
const env = {
|
|
954
970
|
...process.env,
|
|
955
971
|
...secrets,
|
|
@@ -965,13 +981,14 @@ async function cmdRunLocal(args) {
|
|
|
965
981
|
...(remoteState ? {} : { CODIEDEV_STATE_PATH: path.join(work, "state.json") }),
|
|
966
982
|
PYTHONPATH: [path.join(work, "py"), process.env.PYTHONPATH].filter(Boolean).join(path.delimiter),
|
|
967
983
|
...(config ? { CODIEDEV_AGENTS_URL: config.backendUrl, CODIEDEV_TOKEN: config.token } : {}),
|
|
984
|
+
...(model ? { CODIEDEV_MODEL: model } : {}),
|
|
968
985
|
};
|
|
969
986
|
const ext = path.extname(entrypoint);
|
|
970
987
|
const [cmd, ...cmdArgs] = ext === ".py" ? [process.env.PYTHON ?? "python3", "-u", entrypoint]
|
|
971
988
|
: ext === ".sh" ? ["bash", entrypoint]
|
|
972
989
|
: ext === ".ts" || ext === ".mts" ? ["npx", "--yes", "tsx", entrypoint]
|
|
973
990
|
: [process.execPath, entrypoint];
|
|
974
|
-
process.stderr.write(`[local run of ${name} · ${entrypoint}${envFile ? ` · secrets from ${path.relative(process.cwd(), envFile) || envFile}` : ""} · state ${remoteState ? "from Codiedev" : "in .codiedev/local/state.json"}]\n`);
|
|
991
|
+
process.stderr.write(`[local run of ${name} · ${entrypoint}${envFile ? ` · secrets from ${path.relative(process.cwd(), envFile) || envFile}` : ""} · state ${remoteState ? "from Codiedev" : "in .codiedev/local/state.json"}${model ? ` · model ${model}` : ""}]\n`);
|
|
975
992
|
const started = Date.now();
|
|
976
993
|
const res = (0, child_process_1.spawnSync)(cmd, cmdArgs, { cwd: dir, env, stdio: ["inherit", process.stderr, process.stderr] });
|
|
977
994
|
const took = `${((Date.now() - started) / 1000).toFixed(1)}s`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.jsPackage = exports.jsTypes = exports.jsIndex = exports.python = void 0;
|
|
4
|
-
exports.python = "\"\"\"Codiedev agents SDK — the run primitive, from Python.\n\nInside a deployed agent (everything is already configured):\n\n import codiedev_agents as agents\n\n task = agents.input() # this run's input (JSON)\n llm = agents.openai() # OpenAI client on the Codiedev gateway, traced\n child = agents.run(\"summarize\", {\"text\": ...}) # start another agent as a child run\n agents.output({\"summary\": child[\"output\"]}) # this run's result\n agents.request() # webhook runs: method, headers, raw body\n agents.respond({\"text\": \"on it\"}) # what a webhook caller waiting (?wait=) gets back\n\n agents.state.get(\"cursor\") # per-agent storage that outlives runs\n agents.state.set(\"cursor\", 42)\n agents.step(\"fetch\", lambda: fetch()) # done once per run, even across retries\n agents.start(\"triage\", {...}, delay=3600) # start a run later (up to 7 days)\n agents.request_approval(\"Refund $120?\", context={...}) # a person decides; a run acts on it\n agents.attempt() # 1, or 2+ when this run is a retry\n\nOutside a run (a script, a notebook, another service), set CODIEDEV_TOKEN\n(your CLI token) and CODIEDEV_AGENTS_URL, then call agents.run() the same way.\nStandard library only, so it drops into any project.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom typing import Any\n\n__all__ = [\n \"input\", \"output\", \"request\", \"respond\", \"run\", \"start\", \"get\", \"openai\", \"attempt\",\n \"state\", \"step\", \"request_approval\", \"get_approval\", \"AgentRunError\", \"StateConflict\",\n]\n\n_INPUT_PATH = os.environ.get(\"CODIEDEV_INPUT_PATH\", \"/agent/.codiedev/input.json\")\n_OUTPUT_PATH = os.environ.get(\"CODIEDEV_OUTPUT_PATH\", \"/agent/.codiedev/output.json\")\n_REQUEST_PATH = os.environ.get(\"CODIEDEV_REQUEST_PATH\", \"/agent/.codiedev/request.json\")\n_RESPONSE_PATH = os.environ.get(\"CODIEDEV_RESPONSE_PATH\", \"/agent/.codiedev/response.json\")\n# Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\n_STATE_PATH = os.environ.get(\"CODIEDEV_STATE_PATH\")\n_DONE = {\"succeeded\", \"failed\", \"cancelled\"}\n\n\nclass StateConflict(RuntimeError):\n \"\"\"state.set(..., if_version=n) found the key at another version.\"\"\"\n\n def __init__(self, key: str, version: int):\n super().__init__(f\"state {key!r} changed (now version {version})\")\n self.key = key\n self.version = version\n\n\nclass AgentRunError(RuntimeError):\n def __init__(self, run: dict):\n super().__init__(f\"{run.get('agent')} run {run.get('id')} {run.get('status')}: {run.get('error') or 'no error message'}\")\n self.run = run\n\n\ndef input(default: Any = None) -> Any: # noqa: A001 - mirrors output()\n \"\"\"This run's input, parsed from JSON (None when started without one).\"\"\"\n try:\n with open(_INPUT_PATH) as f:\n value = json.load(f)\n except FileNotFoundError:\n return default\n return default if value is None else value\n\n\ndef output(value: Any) -> None:\n \"\"\"Set this run's result. Must be JSON-serializable; the last call wins.\"\"\"\n os.makedirs(os.path.dirname(_OUTPUT_PATH), exist_ok=True)\n with open(_OUTPUT_PATH, \"w\") as f:\n json.dump(value, f)\n\n\ndef respond(body: Any = None, status: int = 200, headers: \"dict | None\" = None) -> None:\n \"\"\"What a webhook caller gets back when it waits for the run (?wait=N):\n a string is sent as text, anything else as JSON. Without respond(), a\n waiting caller gets the run's output.\"\"\"\n os.makedirs(os.path.dirname(_RESPONSE_PATH), exist_ok=True)\n with open(_RESPONSE_PATH, \"w\") as f:\n json.dump({\"status\": status, \"headers\": headers or {}, \"body\": body}, f)\n\n\ndef attempt() -> int:\n \"\"\"This run's attempt: 1, or 2+ when it's a retry of a failed attempt.\"\"\"\n return int(os.environ.get(\"CODIEDEV_ATTEMPT\", \"1\"))\n\n\ndef request() -> \"dict | None\":\n \"\"\"For a webhook run: {\"method\", \"query\", \"headers\", \"body\"} with the raw\n body, to verify the sender's signature. None for other runs.\"\"\"\n try:\n with open(_REQUEST_PATH) as f:\n return json.load(f)\n except FileNotFoundError:\n return None\n\n\ndef _base() -> str:\n url = os.environ.get(\"CODIEDEV_AGENTS_URL\", \"\").rstrip(\"/\")\n if not url:\n raise RuntimeError(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\")\n return url\n\n\ndef _token() -> str:\n # Inside a run: the run's token. A connected agent (running outside Codiedev): its own key.\n # Otherwise: your CLI token.\n key = os.environ.get(\"CODIEDEV_API_KEY\", \"\")\n token = os.environ.get(\"CODIEDEV_RUN_TOKEN\") or (key if key.startswith(\"cdvm_\") else None) or os.environ.get(\"CODIEDEV_TOKEN\")\n if not token:\n raise RuntimeError(\"Set CODIEDEV_API_KEY to your connected agent's key, or CODIEDEV_TOKEN to your CLI token (inside a run it's set for you)\")\n return token\n\n\ndef _request(method: str, path: str, body: dict | None = None, ok: tuple[int, ...] = ()) -> dict:\n req = urllib.request.Request(\n f\"{_base()}{path}\",\n method=method,\n data=json.dumps(body).encode() if body is not None else None,\n headers={\"Authorization\": f\"Bearer {_token()}\", \"Content-Type\": \"application/json\"},\n )\n try:\n with urllib.request.urlopen(req, timeout=30) as res:\n return json.loads(res.read() or b\"{}\")\n except urllib.error.HTTPError as err:\n detail = err.read().decode(errors=\"replace\")\n if err.code in ok:\n return {\"_status\": err.code, **json.loads(detail or \"{}\")}\n try:\n detail = json.loads(detail).get(\"error\", detail)\n except ValueError:\n pass\n raise RuntimeError(f\"Codiedev agents API {err.code}: {detail}\") from None\n\n\ndef start(\n agent: str,\n input: Any = None, # noqa: A002\n idempotency_key: str | None = None,\n delay: float | None = None,\n retries: int | None = None,\n) -> str:\n \"\"\"Start a run and return its id without waiting. With an idempotency key,\n a second start within a day returns the first run's id. delay (seconds,\n up to 7 days) starts it later; retries overrides the agent's retries.\"\"\"\n body: dict = {\"agent\": agent, \"input\": input}\n if idempotency_key:\n body[\"idempotencyKey\"] = idempotency_key\n if delay:\n body[\"delaySec\"] = int(delay)\n if retries is not None:\n body[\"retries\"] = retries\n return _request(\"POST\", \"/api/agents/v1/run\", body)[\"runId\"]\n\n\ndef get(run_id: str) -> dict:\n \"\"\"A run's status, input, output (parsed), error, log tail and children.\"\"\"\n run = _request(\"GET\", f\"/api/agents/v1/run?id={run_id}\")\n if isinstance(run.get(\"output\"), str):\n try:\n run[\"output\"] = json.loads(run[\"output\"])\n except ValueError:\n pass\n return run\n\n\ndef run(\n agent: str,\n input: Any = None, # noqa: A002\n timeout: float = 900,\n poll: float = 2.0,\n check: bool = True,\n idempotency_key: str | None = None,\n) -> dict:\n \"\"\"Run an agent and wait for it. Returns the run; its result is run[\"output\"].\n\n Inside a run this starts a child run, so the chain shows as one tree.\n Raises AgentRunError if it fails (pass check=False to get the run anyway).\n \"\"\"\n run_id = start(agent, input, idempotency_key)\n deadline = time.monotonic() + timeout\n while True:\n result = get(run_id)\n if result.get(\"status\") in _DONE:\n if check and result[\"status\"] != \"succeeded\":\n raise AgentRunError(result)\n return result\n if time.monotonic() > deadline:\n raise TimeoutError(f\"{agent} run {run_id} still {result.get('status')} after {timeout}s\")\n time.sleep(poll)\n\n\nclass _State:\n \"\"\"Per-agent key-value storage that outlives runs (values: any JSON, up\n to 64 KB; 1,000 keys). Every write bumps the key's version; pass\n if_version to write only if nobody else did since you read it.\"\"\"\n\n def get(self, key: str, default: Any = None) -> Any:\n value, _ = self.get_versioned(key)\n return default if value is None else value\n\n def get_versioned(self, key: str) -> \"tuple[Any, int]\":\n \"\"\"(value, version); (None, 0) when the key isn't set.\"\"\"\n if _STATE_PATH:\n row = _local_state().get(key)\n return (row[\"value\"], row[\"version\"]) if row else (None, 0)\n res = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode({'key': key, 'agent': _agent()})}\", ok=(404,))\n if res.get(\"_status\") == 404:\n return None, 0\n return json.loads(res[\"value\"]), int(res[\"version\"])\n\n def set(self, key: str, value: Any, if_version: \"int | None\" = None, ttl: \"float | None\" = None) -> int:\n \"\"\"Store value; returns the new version. Raises StateConflict when\n if_version is given and the key is at another version (0 = unset).\"\"\"\n return self._write(key, value, if_version, ttl)\n\n def delete(self, key: str, if_version: \"int | None\" = None) -> None:\n self._write(key, None, if_version, None)\n\n def list(self, prefix: str = \"\", values: bool = False) -> \"list[dict]\": # noqa: A003\n \"\"\"Keys (and with values=True, values) starting with prefix.\"\"\"\n if _STATE_PATH:\n rows = sorted((k, r) for k, r in _local_state().items() if k.startswith(prefix))\n return [{\"key\": k, \"version\": r[\"version\"], **({\"value\": r[\"value\"]} if values else {})} for k, r in rows]\n keys: list = []\n after = None\n while True:\n params = {\"prefix\": prefix, \"values\": \"1\" if values else \"0\", \"limit\": 200, \"agent\": _agent()}\n if after:\n params[\"after\"] = after\n page = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode(params)}\")\n for row in page[\"keys\"]:\n if \"value\" in row:\n row[\"value\"] = json.loads(row[\"value\"])\n keys.extend(page[\"keys\"])\n after = page.get(\"next\")\n if not after:\n return keys\n\n def update(self, key: str, fn, default: Any = None, retries: int = 10) -> Any:\n \"\"\"Read-modify-write that's safe with runs in parallel: fn(old) -> new,\n retried if another run wrote first. Returns the new value.\"\"\"\n for _ in range(retries):\n value, version = self.get_versioned(key)\n new = fn(default if value is None else value)\n try:\n self.set(key, new, if_version=version)\n return new\n except StateConflict:\n time.sleep(0.05)\n raise StateConflict(key, -1)\n\n def _write(self, key: str, value: Any, if_version: \"int | None\", ttl: \"float | None\") -> int:\n if _STATE_PATH:\n data = _local_state()\n current = data.get(key, {}).get(\"version\", 0)\n if if_version is not None and if_version != current:\n raise StateConflict(key, current)\n if value is None:\n data.pop(key, None)\n else:\n data[key] = {\"value\": value, \"version\": current + 1}\n with open(_STATE_PATH, \"w\") as f:\n json.dump(data, f, indent=2)\n return 0 if value is None else current + 1\n body: dict = {\"key\": key, \"value\": value, \"agent\": _agent()}\n if if_version is not None:\n body[\"ifVersion\"] = if_version\n if ttl:\n body[\"ttlSec\"] = int(ttl)\n res = _request(\"POST\", \"/api/agents/v1/state\", body, ok=(409,))\n if res.get(\"_status\") == 409:\n raise StateConflict(key, int(res.get(\"version\", 0)))\n return int(res.get(\"version\", 0))\n\n\ndef _agent() -> str:\n # Inside a run the run token picks the agent; a CLI token needs it named.\n return os.environ.get(\"CODIEDEV_AGENT\", \"\")\n\n\ndef _local_state() -> dict:\n try:\n with open(_STATE_PATH or \"\") as f:\n return json.load(f)\n except (FileNotFoundError, ValueError):\n return {}\n\n\nstate = _State()\n\n\ndef step(name: str, fn, ttl: float = 7 * 86400) -> Any:\n \"\"\"Run fn() once for this run and remember its (JSON) result: if the run\n is retried after a failure, finished steps return their saved result\n instead of running again. Use it around side effects (posting a reply,\n charging a card) so a retry doesn't repeat them.\"\"\"\n key = f\"step/{os.environ.get('CODIEDEV_RUN_ID', 'local')}/{name}\"\n value, version = state.get_versioned(key)\n if version:\n return value[\"result\"]\n result = fn()\n state.set(key, {\"result\": result}, ttl=ttl)\n return result\n\n\n_SELF = object()\n\n\ndef request_approval(\n title: str,\n details: Any = None,\n options: \"list[str] | None\" = None,\n approvers: \"list[str] | None\" = None,\n teams: \"list[str] | None\" = None,\n fields: \"list[dict] | None\" = None,\n then: Any = _SELF,\n context: Any = None,\n expires_in: \"float | None\" = None,\n) -> dict:\n \"\"\"Ask a person to decide. It shows in Codiedev (Agents → Approvals) and\n at the returned [\"url\"], which you can post anywhere (Slack, email).\n\n When it's decided, a run of `then` (this agent by default; None for no\n run) starts with input {\"approval\": {\"decision\", \"comment\", \"decidedBy\",\n ...}, \"context\": context}. If nobody decides within expires_in seconds\n (default 7 days), it starts with decision \"expired\". Usually the run\n ends after asking, so nothing runs (or costs) while it waits.\n options default to [\"approve\", \"reject\"]; approvers (emails) and teams\n (team names, e.g. [\"Finance\"]) limit who can decide (admins always can).\n Apps your company connects (Agents → Approvals → Apps) are told about it\n by webhook and can decide on a person's behalf.\n\n fields asks the person to fill something in as they decide, e.g.\n [{\"name\": \"rating\", \"type\": \"rating\"}, {\"name\": \"why\", \"type\": \"textarea\"}]\n (types: text, textarea, number, rating, select, multiselect, boolean;\n required, label, help, options, min, max, default). What they entered\n arrives as input[\"approval\"][\"values\"]. With fields and no options, the\n only choice is \"submit\".\"\"\"\n body: dict = {\"title\": title, \"details\": details, \"options\": options, \"approvers\": approvers, \"teams\": teams, \"fields\": fields, \"context\": context}\n if then is not _SELF:\n body[\"onDecision\"] = then\n if expires_in:\n body[\"expiresSec\"] = int(expires_in)\n if os.environ.get(\"CODIEDEV_LOCAL\") == \"1\":\n print(f\"[local] approval requested: {title} ({', '.join(options or (['submit'] if fields else ['approve', 'reject']))}); not sent on a local run\", flush=True)\n return {\"id\": f\"local-{int(time.time())}\", \"status\": \"pending\", \"url\": None, \"title\": title}\n return _request(\"POST\", \"/api/agents/v1/approvals\", {k: v for k, v in body.items() if v is not None or k == \"onDecision\"})\n\n\ndef get_approval(approval_id: str) -> dict:\n \"\"\"An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment.\"\"\"\n return _request(\"GET\", f\"/api/agents/v1/approvals?id={urllib.parse.quote(approval_id)}\")\n\n\ndef wait_for_approval(approval_id: str, timeout: float = 3600, poll: float = 15) -> dict:\n \"\"\"Wait until a person decides (or it expires), then return the approval. For agents that keep\n running while they wait, like a connected agent; a run inside Codiedev usually ends instead and\n is started again with the decision.\"\"\"\n deadline = time.time() + timeout\n while True:\n a = get_approval(approval_id)\n if a.get(\"status\") != \"pending\":\n return a\n if time.time() > deadline:\n raise TimeoutError(f\"approval {approval_id} still pending after {timeout}s\")\n time.sleep(poll)\n\n\ndef openai(**kwargs):\n \"\"\"An OpenAI client on the Codiedev gateway, with trace headers for this run.\"\"\"\n from openai import OpenAI\n\n headers = {\n \"X-Codiedev-Workload\": os.environ.get(\"CODIEDEV_AGENT\", \"local-agent\"),\n \"X-Codiedev-Run-Id\": os.environ.get(\"CODIEDEV_RUN_ID\", f\"local-{int(time.time())}\"),\n \"X-Codiedev-Environment\": \"production\" if os.environ.get(\"CODIEDEV_RUN_ID\") else \"development\",\n }\n headers.update(kwargs.pop(\"default_headers\", {}) or {})\n return OpenAI(\n base_url=os.environ.get(\"CODIEDEV_GATEWAY_URL\", \"https://gateway.codiedev.com/api/v1\"),\n api_key=os.environ.get(\"CODIEDEV_API_KEY\") or os.environ.get(\"CODIEDEV_TOKEN\"),\n default_headers=headers,\n **kwargs,\n )\n";
|
|
5
|
-
exports.jsIndex = "// Codiedev agents SDK for JavaScript and TypeScript, preinstalled in every\n// run. Same primitive as the Python SDK (codiedev_agents.py):\n//\n// import * as agents from \"codiedev-agents\";\n// const task = agents.input(); // this run's input (JSON)\n// const llm = await agents.openai(); // OpenAI client on the Codiedev gateway, traced\n// const child = await agents.run(\"summarize\", { text }); // another agent, as a child run\n// agents.output({ summary: child.output }); // this run's result\n// agents.respond({ text: \"on it\" }); // what a webhook caller waiting (?wait=) gets back\n// await agents.state.get(\"cursor\"); // per-agent storage that outlives runs\n// await agents.step(\"post\", () => post()); // done once per run, even across retries\n// await agents.start(\"triage\", input, { delaySec: 3600 }); // start a run later\n// await agents.requestApproval(\"Refund $120?\", { context }); // a person decides; a run acts on it\n//\n// Outside a run, set CODIEDEV_TOKEN and CODIEDEV_AGENTS_URL and call run() the same way.\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nconst INPUT_PATH = process.env.CODIEDEV_INPUT_PATH || \"/agent/.codiedev/input.json\";\nconst OUTPUT_PATH = process.env.CODIEDEV_OUTPUT_PATH || \"/agent/.codiedev/output.json\";\nconst REQUEST_PATH = process.env.CODIEDEV_REQUEST_PATH || \"/agent/.codiedev/request.json\";\nconst RESPONSE_PATH = process.env.CODIEDEV_RESPONSE_PATH || \"/agent/.codiedev/response.json\";\n// Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\nconst STATE_PATH = process.env.CODIEDEV_STATE_PATH;\n// Inside a run the run token picks the agent; a CLI token needs it named.\nconst AGENT = process.env.CODIEDEV_AGENT || \"\";\nconst DONE = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n\n/** state.set(..., { ifVersion }) found the key at another version. */\nexport class StateConflict extends Error {\n constructor(key, version) {\n super(`state ${JSON.stringify(key)} changed (now version ${version})`);\n this.key = key;\n this.version = version;\n }\n}\n\nexport class AgentRunError extends Error {\n constructor(run) {\n super(`${run.agent} run ${run.id} ${run.status}: ${run.error || \"no error message\"}`);\n this.run = run;\n }\n}\n\nfunction readJson(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch (err) {\n if (err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** This run's input, parsed from JSON (`fallback` when started without one). */\nexport function input(fallback = null) {\n const value = readJson(INPUT_PATH);\n return value ?? fallback;\n}\n\n/** Set this run's result. Must be JSON-serializable; the last call wins. */\nexport function output(value) {\n fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });\n fs.writeFileSync(OUTPUT_PATH, JSON.stringify(value));\n}\n\n/** What a webhook caller gets back when it waits for the run (?wait=N): a\n * string is sent as text, anything else as JSON. Without it, a waiting\n * caller gets the run's output. */\nexport function respond(body = null, { status = 200, headers = {} } = {}) {\n fs.mkdirSync(path.dirname(RESPONSE_PATH), { recursive: true });\n fs.writeFileSync(RESPONSE_PATH, JSON.stringify({ status, headers, body }));\n}\n\n/** This run's attempt: 1, or 2+ when it's a retry of a failed attempt. */\nexport function attempt() {\n return Number(process.env.CODIEDEV_ATTEMPT || \"1\");\n}\n\n/** For a webhook run: { method, query, headers, body } with the raw body, to verify a signature. */\nexport function request() {\n return readJson(REQUEST_PATH);\n}\n\nfunction base() {\n const url = (process.env.CODIEDEV_AGENTS_URL || \"\").replace(/\\/$/, \"\");\n if (!url) throw new Error(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\");\n return url;\n}\n\nfunction token() {\n // Inside a run: the run's token. A connected agent (running outside Codiedev): its own key.\n // Otherwise: your CLI token.\n const key = process.env.CODIEDEV_API_KEY || \"\";\n const t = process.env.CODIEDEV_RUN_TOKEN || (key.startsWith(\"cdvm_\") ? key : \"\") || process.env.CODIEDEV_TOKEN;\n if (!t) throw new Error(\"Set CODIEDEV_API_KEY to your connected agent's key, or CODIEDEV_TOKEN to your CLI token (inside a run it's set for you)\");\n return t;\n}\n\nasync function call(method, route, body, ok = []) {\n const res = await fetch(`${base()}${route}`, {\n method,\n headers: { Authorization: `Bearer ${token()}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await res.text();\n let data = {};\n try {\n data = text ? JSON.parse(text) : {};\n } catch {\n data = { error: text };\n }\n if (ok.includes(res.status)) return { _status: res.status, ...data };\n if (!res.ok) throw new Error(`Codiedev agents API ${res.status}: ${data.error || text}`);\n return data;\n}\n\n/** Start a run and return its id without waiting. delaySec (up to 7 days)\n * starts it later; retries overrides the agent's retries. */\nexport async function start(agent, input = null, { idempotencyKey, delaySec, retries } = {}) {\n return (await call(\"POST\", \"/api/agents/v1/run\", {\n agent,\n input,\n ...(idempotencyKey ? { idempotencyKey } : {}),\n ...(delaySec ? { delaySec } : {}),\n ...(retries !== undefined ? { retries } : {}),\n })).runId;\n}\n\nfunction localState() {\n try {\n return JSON.parse(fs.readFileSync(STATE_PATH, \"utf8\"));\n } catch {\n return {};\n }\n}\n\nasync function writeState(key, value, { ifVersion, ttlSec } = {}) {\n if (STATE_PATH) {\n const data = localState();\n const current = data[key]?.version ?? 0;\n if (ifVersion !== undefined && ifVersion !== current) throw new StateConflict(key, current);\n if (value === null) delete data[key];\n else data[key] = { value, version: current + 1 };\n fs.writeFileSync(STATE_PATH, JSON.stringify(data, null, 2));\n return value === null ? 0 : current + 1;\n }\n const res = await call(\"POST\", \"/api/agents/v1/state\", { key, value, agent: AGENT, ...(ifVersion !== undefined ? { ifVersion } : {}), ...(ttlSec ? { ttlSec } : {}) }, [409]);\n if (res._status === 409) throw new StateConflict(key, res.version ?? 0);\n return res.version ?? 0;\n}\n\n/** Per-agent key-value storage that outlives runs (values: any JSON, up to\n * 64 KB; 1,000 keys). Every write bumps the key's version; pass ifVersion\n * to write only if nobody else did since you read it. */\nexport const state = {\n async get(key, fallback = null) {\n const { value } = await state.getVersioned(key);\n return value ?? fallback;\n },\n /** { value, version }; { value: null, version: 0 } when the key isn't set. */\n async getVersioned(key) {\n if (STATE_PATH) {\n const row = localState()[key];\n return row ? { value: row.value, version: row.version } : { value: null, version: 0 };\n }\n const res = await call(\"GET\", `/api/agents/v1/state?${new URLSearchParams({ key, agent: AGENT })}`, undefined, [404]);\n if (res._status === 404) return { value: null, version: 0 };\n return { value: JSON.parse(res.value), version: res.version };\n },\n /** Store value; returns the new version. Throws StateConflict on an ifVersion mismatch (0 = unset). */\n set(key, value, { ifVersion, ttlSec } = {}) {\n return writeState(key, value, { ifVersion, ttlSec });\n },\n async delete(key, { ifVersion } = {}) {\n await writeState(key, null, { ifVersion });\n },\n /** Keys (and with values: true, values) starting with prefix. */\n async list(prefix = \"\", { values = false } = {}) {\n if (STATE_PATH) {\n return Object.entries(localState())\n .filter(([k]) => k.startsWith(prefix))\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, row]) => ({ key, version: row.version, ...(values ? { value: row.value } : {}) }));\n }\n const out = [];\n let after = null;\n do {\n const q = new URLSearchParams({ prefix, values: values ? \"1\" : \"0\", limit: \"200\", agent: AGENT, ...(after ? { after } : {}) });\n const page = await call(\"GET\", `/api/agents/v1/state?${q}`);\n out.push(...page.keys.map((row) => (\"value\" in row ? { ...row, value: JSON.parse(row.value) } : row)));\n after = page.next;\n } while (after);\n return out;\n },\n /** Read-modify-write that's safe with runs in parallel: fn(old) => new, retried if another run wrote first. */\n async update(key, fn, fallback = null, retries = 10) {\n for (let i = 0; i < retries; i++) {\n const { value, version } = await state.getVersioned(key);\n const next = await fn(value ?? fallback);\n try {\n await state.set(key, next, { ifVersion: version });\n return next;\n } catch (err) {\n if (!(err instanceof StateConflict)) throw err;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n }\n throw new StateConflict(key, -1);\n },\n};\n\n/** Run fn() once for this run and remember its (JSON) result: if the run is\n * retried after a failure, finished steps return their saved result instead\n * of running again. Use it around side effects so a retry doesn't repeat them. */\nexport async function step(name, fn, { ttlSec = 7 * 86400 } = {}) {\n const key = `step/${process.env.CODIEDEV_RUN_ID || \"local\"}/${name}`;\n const { value, version } = await state.getVersioned(key);\n if (version) return value.result;\n const result = await fn();\n await state.set(key, { result: result ?? null }, { ttlSec });\n return result;\n}\n\n/** Ask a person to decide. It shows in Codiedev (Agents → Approvals) and at\n * the returned .url, which you can post anywhere. When it's decided, a run of\n * `then` (this agent by default; null for none) starts with input\n * { approval: { decision, comment, decidedBy, ... }, context }. Undecided\n * after expiresSec (default 7 days): decision \"expired\". */\nexport async function requestApproval(title, { details, options, approvers, teams, fields, then, context, expiresSec } = {}) {\n if (process.env.CODIEDEV_LOCAL === \"1\") {\n console.log(`[local] approval requested: ${title} (${(options || (fields ? [\"submit\"] : [\"approve\", \"reject\"])).join(\", \")}); not sent on a local run`);\n return { id: `local-${Date.now()}`, status: \"pending\", url: null, title };\n }\n return call(\"POST\", \"/api/agents/v1/approvals\", {\n title,\n ...(details !== undefined ? { details } : {}),\n ...(options ? { options } : {}),\n ...(approvers ? { approvers } : {}),\n ...(teams ? { teams } : {}),\n ...(fields ? { fields } : {}),\n ...(then !== undefined ? { onDecision: then } : {}),\n ...(context !== undefined ? { context } : {}),\n ...(expiresSec ? { expiresSec } : {}),\n });\n}\n\n/** An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment. */\nexport async function getApproval(id) {\n return call(\"GET\", `/api/agents/v1/approvals?id=${encodeURIComponent(id)}`);\n}\n\n/** Wait until a person decides (or it expires), then return the approval. For agents that keep\n * running while they wait, like a connected agent; a run inside Codiedev usually ends instead and\n * is started again with the decision. */\nexport async function waitForApproval(id, { timeoutMs = 3_600_000, pollMs = 15_000 } = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const a = await getApproval(id);\n if (a.status !== \"pending\") return a;\n if (Date.now() > deadline) throw new Error(`approval ${id} still pending after ${timeoutMs / 1000}s`);\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\n/** A run's status, input, output (parsed), error, log and children. */\nexport async function get(runId) {\n const run = await call(\"GET\", `/api/agents/v1/run?id=${encodeURIComponent(runId)}`);\n if (typeof run.output === \"string\") {\n try {\n run.output = JSON.parse(run.output);\n } catch {\n // not JSON: keep the string\n }\n }\n return run;\n}\n\n/** Run an agent and wait for it; its result is `.output`. Inside a run this\n * starts a child run. Throws AgentRunError if it fails unless check is false. */\nexport async function run(agent, input = null, { timeoutMs = 900_000, pollMs = 2000, check = true, idempotencyKey } = {}) {\n const id = await start(agent, input, { idempotencyKey });\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const r = await get(id);\n if (DONE.has(r.status)) {\n if (check && r.status !== \"succeeded\") throw new AgentRunError(r);\n return r;\n }\n if (Date.now() > deadline) throw new Error(`${agent} run ${id} still ${r.status} after ${timeoutMs / 1000}s`);\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\n/** Options for `new OpenAI(...)` on the Codiedev gateway, with this run's trace headers. */\nexport function openaiOptions(extra = {}) {\n const inRun = !!process.env.CODIEDEV_RUN_ID;\n return {\n baseURL: process.env.CODIEDEV_GATEWAY_URL || \"https://gateway.codiedev.com/api/v1\",\n apiKey: process.env.CODIEDEV_API_KEY || process.env.CODIEDEV_TOKEN,\n ...extra,\n defaultHeaders: {\n \"X-Codiedev-Workload\": process.env.CODIEDEV_AGENT || \"local-agent\",\n \"X-Codiedev-Run-Id\": process.env.CODIEDEV_RUN_ID || `local-${Date.now()}`,\n \"X-Codiedev-Environment\": inRun ? \"production\" : \"development\",\n ...(extra.defaultHeaders || {}),\n },\n };\n}\n\n/** An OpenAI client on the Codiedev gateway (the `openai` package is preinstalled). */\nexport async function openai(extra = {}) {\n const { default: OpenAI } = await import(\"openai\");\n return new OpenAI(openaiOptions(extra));\n}\n";
|
|
6
|
-
exports.jsTypes = "/* eslint-disable @typescript-eslint/no-explicit-any -- defaults are `any` on purpose: agent code reads its own JSON input/state without declaring types first. */\nexport type RunStatus = \"queued\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\";\nexport interface Run<O = unknown> {\n id: string;\n agent: string;\n version: number;\n status: RunStatus;\n trigger: \"cli\" | \"api\" | \"portal\" | \"schedule\" | \"webhook\" | \"child\" | \"approval\";\n parentRunId: string | null;\n rootRunId: string | null;\n depth: number;\n attempt: number;\n maxAttempts: number;\n scheduledFor: number | null;\n input: string | null;\n output: O;\n error: string | null;\n logTail: string | null;\n durationMs: number | null;\n children: Run[];\n}\nexport interface WebhookRequest { method: string; query: Record<string, string>; headers: Record<string, string>; body: string }\nexport interface Approval {\n id: string;\n agent: string;\n title: string;\n details: string | null;\n options: string[];\n status: \"pending\" | \"decided\" | \"expired\" | \"cancelled\";\n decision: string | null;\n comment: string | null;\n decidedBy: string | null;\n /** The approval app that sent the decision on decidedBy's behalf; null when decided in Codiedev. */\n decidedVia: string | null;\n approvers: string[];\n approverTeams: Array<{ id: string; name: string }>;\n /** The form the person fills in, if the approval asked for one. */\n fields: ApprovalField[];\n /** What they entered, by field name (null until decided). */\n values: Record<string, any> | null;\n url: string | null;\n resumeRunId: string | null;\n}\n/** One thing a person fills in when they decide. */\nexport interface ApprovalField {\n name: string;\n type: \"text\" | \"textarea\" | \"number\" | \"rating\" | \"select\" | \"multiselect\" | \"boolean\";\n label?: string;\n help?: string;\n required?: boolean;\n /** select / multiselect choices */\n options?: string[];\n /** number bounds; rating scale 1–max (default 5) */\n min?: number;\n max?: number;\n /** prefilled, e.g. a proposed amount for the person to correct */\n default?: unknown;\n}\n/** The input a run started by a decision gets. */\nexport interface ApprovalInput<C = any, V = Record<string, any>> {\n approval: { id: string; title: string; decision: string; comment: string | null; values: V; decidedBy: string | null; decidedVia: string | null; decidedAt: number; requestedByRun: string | null };\n context: C;\n}\nexport class AgentRunError extends Error { run: Run }\nexport class StateConflict extends Error { key: string; version: number }\nexport function input<T = any>(fallback?: T): T;\nexport function output(value: unknown): void;\nexport function request(): WebhookRequest | null;\nexport function respond(body?: unknown, opts?: { status?: number; headers?: Record<string, string> }): void;\nexport function attempt(): number;\nexport function start(agent: string, input?: unknown, opts?: { idempotencyKey?: string; delaySec?: number; retries?: number }): Promise<string>;\nexport function get<O = any>(runId: string): Promise<Run<O>>;\nexport function run<O = any>(agent: string, input?: unknown, opts?: { timeoutMs?: number; pollMs?: number; check?: boolean; idempotencyKey?: string }): Promise<Run<O>>;\nexport const state: {\n get<T = any>(key: string, fallback?: T): Promise<T>;\n getVersioned<T = any>(key: string): Promise<{ value: T | null; version: number }>;\n set(key: string, value: unknown, opts?: { ifVersion?: number; ttlSec?: number }): Promise<number>;\n delete(key: string, opts?: { ifVersion?: number }): Promise<void>;\n list(prefix?: string, opts?: { values?: boolean }): Promise<Array<{ key: string; version: number; value?: any }>>;\n update<T = any>(key: string, fn: (old: T) => T | Promise<T>, fallback?: T, retries?: number): Promise<T>;\n};\nexport function step<T>(name: string, fn: () => T | Promise<T>, opts?: { ttlSec?: number }): Promise<T>;\nexport function requestApproval<C = any>(title: string, opts?: { details?: unknown; options?: string[]; approvers?: string[]; teams?: string[]; fields?: ApprovalField[]; then?: string | null; context?: C; expiresSec?: number }): Promise<Approval>;\nexport function getApproval(id: string): Promise<Approval>;\nexport function waitForApproval(id: string, opts?: { timeoutMs?: number; pollMs?: number }): Promise<Approval>;\nexport function openaiOptions(extra?: Record<string, unknown>): { baseURL: string; apiKey: string | undefined; defaultHeaders: Record<string, string> };\nexport function openai(extra?: Record<string, unknown>): Promise<import(\"openai\").default
|
|
4
|
+
exports.python = "\"\"\"Codiedev agents SDK — the run primitive, from Python.\n\nInside a deployed agent (everything is already configured):\n\n import codiedev_agents as agents\n\n task = agents.input() # this run's input (JSON)\n llm = agents.openai() # OpenAI client on the Codiedev gateway, traced\n child = agents.run(\"summarize\", {\"text\": ...}) # start another agent as a child run\n agents.output({\"summary\": child[\"output\"]}) # this run's result\n agents.request() # webhook runs: method, headers, raw body\n agents.respond({\"text\": \"on it\"}) # what a webhook caller waiting (?wait=) gets back\n\n agents.state.get(\"cursor\") # per-agent storage that outlives runs\n agents.state.set(\"cursor\", 42)\n agents.step(\"fetch\", lambda: fetch()) # done once per run, even across retries\n agents.start(\"triage\", {...}, delay=3600) # start a run later (up to 7 days)\n agents.request_approval(\"Refund $120?\", context={...}) # a person decides; a run acts on it\n agents.attempt() # 1, or 2+ when this run is a retry\n agents.tasks.add(\"WEB\", \"Customer can't log in\", body=...) # Projects (when your company has it)\n agents.ping(\"sam\", \"Weekly digest: ...\") # message a teammate (shows which agent sent it)\n\nOutside a run (a script, a notebook, another service), set CODIEDEV_TOKEN\n(your CLI token) and CODIEDEV_AGENTS_URL, then call agents.run() the same way.\nStandard library only, so it drops into any project.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport time\nimport urllib.error\nimport urllib.parse\nimport urllib.request\nfrom typing import Any\n\n__all__ = [\n \"input\", \"output\", \"request\", \"respond\", \"run\", \"start\", \"get\", \"openai\", \"attempt\",\n \"state\", \"step\", \"request_approval\", \"get_approval\", \"AgentRunError\", \"StateConflict\",\n]\n\n_INPUT_PATH = os.environ.get(\"CODIEDEV_INPUT_PATH\", \"/agent/.codiedev/input.json\")\n_OUTPUT_PATH = os.environ.get(\"CODIEDEV_OUTPUT_PATH\", \"/agent/.codiedev/output.json\")\n_REQUEST_PATH = os.environ.get(\"CODIEDEV_REQUEST_PATH\", \"/agent/.codiedev/request.json\")\n_RESPONSE_PATH = os.environ.get(\"CODIEDEV_RESPONSE_PATH\", \"/agent/.codiedev/response.json\")\n# Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\n_STATE_PATH = os.environ.get(\"CODIEDEV_STATE_PATH\")\n_DONE = {\"succeeded\", \"failed\", \"cancelled\"}\n\n\nclass StateConflict(RuntimeError):\n \"\"\"state.set(..., if_version=n) found the key at another version.\"\"\"\n\n def __init__(self, key: str, version: int):\n super().__init__(f\"state {key!r} changed (now version {version})\")\n self.key = key\n self.version = version\n\n\nclass AgentRunError(RuntimeError):\n def __init__(self, run: dict):\n super().__init__(f\"{run.get('agent')} run {run.get('id')} {run.get('status')}: {run.get('error') or 'no error message'}\")\n self.run = run\n\n\ndef input(default: Any = None) -> Any: # noqa: A001 - mirrors output()\n \"\"\"This run's input, parsed from JSON (None when started without one).\"\"\"\n try:\n with open(_INPUT_PATH) as f:\n value = json.load(f)\n except FileNotFoundError:\n return default\n return default if value is None else value\n\n\ndef output(value: Any) -> None:\n \"\"\"Set this run's result. Must be JSON-serializable; the last call wins.\"\"\"\n os.makedirs(os.path.dirname(_OUTPUT_PATH), exist_ok=True)\n with open(_OUTPUT_PATH, \"w\") as f:\n json.dump(value, f)\n\n\ndef respond(body: Any = None, status: int = 200, headers: \"dict | None\" = None) -> None:\n \"\"\"What a webhook caller gets back when it waits for the run (?wait=N):\n a string is sent as text, anything else as JSON. Without respond(), a\n waiting caller gets the run's output.\"\"\"\n os.makedirs(os.path.dirname(_RESPONSE_PATH), exist_ok=True)\n with open(_RESPONSE_PATH, \"w\") as f:\n json.dump({\"status\": status, \"headers\": headers or {}, \"body\": body}, f)\n\n\ndef attempt() -> int:\n \"\"\"This run's attempt: 1, or 2+ when it's a retry of a failed attempt.\"\"\"\n return int(os.environ.get(\"CODIEDEV_ATTEMPT\", \"1\"))\n\n\ndef request() -> \"dict | None\":\n \"\"\"For a webhook run: {\"method\", \"query\", \"headers\", \"body\"} with the raw\n body, to verify the sender's signature. None for other runs.\"\"\"\n try:\n with open(_REQUEST_PATH) as f:\n return json.load(f)\n except FileNotFoundError:\n return None\n\n\ndef _base() -> str:\n url = os.environ.get(\"CODIEDEV_AGENTS_URL\", \"\").rstrip(\"/\")\n if not url:\n raise RuntimeError(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\")\n return url\n\n\ndef _token() -> str:\n # Inside a run: the run's token. A connected agent (running outside Codiedev): its own key.\n # Otherwise: your CLI token.\n key = os.environ.get(\"CODIEDEV_API_KEY\", \"\")\n token = os.environ.get(\"CODIEDEV_RUN_TOKEN\") or (key if key.startswith(\"cdvm_\") else None) or os.environ.get(\"CODIEDEV_TOKEN\")\n if not token:\n raise RuntimeError(\"Set CODIEDEV_API_KEY to your connected agent's key, or CODIEDEV_TOKEN to your CLI token (inside a run it's set for you)\")\n return token\n\n\ndef _request(method: str, path: str, body: dict | None = None, ok: tuple[int, ...] = ()) -> dict:\n req = urllib.request.Request(\n f\"{_base()}{path}\",\n method=method,\n data=json.dumps(body).encode() if body is not None else None,\n headers={\"Authorization\": f\"Bearer {_token()}\", \"Content-Type\": \"application/json\"},\n )\n try:\n with urllib.request.urlopen(req, timeout=30) as res:\n return json.loads(res.read() or b\"{}\")\n except urllib.error.HTTPError as err:\n detail = err.read().decode(errors=\"replace\")\n if err.code in ok:\n return {\"_status\": err.code, **json.loads(detail or \"{}\")}\n try:\n detail = json.loads(detail).get(\"error\", detail)\n except ValueError:\n pass\n raise RuntimeError(f\"Codiedev agents API {err.code}: {detail}\") from None\n\n\ndef start(\n agent: str,\n input: Any = None, # noqa: A002\n idempotency_key: str | None = None,\n delay: float | None = None,\n retries: int | None = None,\n) -> str:\n \"\"\"Start a run and return its id without waiting. With an idempotency key,\n a second start within a day returns the first run's id. delay (seconds,\n up to 7 days) starts it later; retries overrides the agent's retries.\"\"\"\n body: dict = {\"agent\": agent, \"input\": input}\n if idempotency_key:\n body[\"idempotencyKey\"] = idempotency_key\n if delay:\n body[\"delaySec\"] = int(delay)\n if retries is not None:\n body[\"retries\"] = retries\n return _request(\"POST\", \"/api/agents/v1/run\", body)[\"runId\"]\n\n\ndef get(run_id: str) -> dict:\n \"\"\"A run's status, input, output (parsed), error, log tail and children.\"\"\"\n run = _request(\"GET\", f\"/api/agents/v1/run?id={run_id}\")\n if isinstance(run.get(\"output\"), str):\n try:\n run[\"output\"] = json.loads(run[\"output\"])\n except ValueError:\n pass\n return run\n\n\ndef run(\n agent: str,\n input: Any = None, # noqa: A002\n timeout: float = 900,\n poll: float = 2.0,\n check: bool = True,\n idempotency_key: str | None = None,\n) -> dict:\n \"\"\"Run an agent and wait for it. Returns the run; its result is run[\"output\"].\n\n Inside a run this starts a child run, so the chain shows as one tree.\n Raises AgentRunError if it fails (pass check=False to get the run anyway).\n \"\"\"\n run_id = start(agent, input, idempotency_key)\n deadline = time.monotonic() + timeout\n while True:\n result = get(run_id)\n if result.get(\"status\") in _DONE:\n if check and result[\"status\"] != \"succeeded\":\n raise AgentRunError(result)\n return result\n if time.monotonic() > deadline:\n raise TimeoutError(f\"{agent} run {run_id} still {result.get('status')} after {timeout}s\")\n time.sleep(poll)\n\n\nclass _State:\n \"\"\"Per-agent key-value storage that outlives runs (values: any JSON, up\n to 64 KB; 1,000 keys). Every write bumps the key's version; pass\n if_version to write only if nobody else did since you read it.\"\"\"\n\n def get(self, key: str, default: Any = None) -> Any:\n value, _ = self.get_versioned(key)\n return default if value is None else value\n\n def get_versioned(self, key: str) -> \"tuple[Any, int]\":\n \"\"\"(value, version); (None, 0) when the key isn't set.\"\"\"\n if _STATE_PATH:\n row = _local_state().get(key)\n return (row[\"value\"], row[\"version\"]) if row else (None, 0)\n res = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode({'key': key, 'agent': _agent()})}\", ok=(404,))\n if res.get(\"_status\") == 404:\n return None, 0\n return json.loads(res[\"value\"]), int(res[\"version\"])\n\n def set(self, key: str, value: Any, if_version: \"int | None\" = None, ttl: \"float | None\" = None) -> int:\n \"\"\"Store value; returns the new version. Raises StateConflict when\n if_version is given and the key is at another version (0 = unset).\"\"\"\n return self._write(key, value, if_version, ttl)\n\n def delete(self, key: str, if_version: \"int | None\" = None) -> None:\n self._write(key, None, if_version, None)\n\n def list(self, prefix: str = \"\", values: bool = False) -> \"list[dict]\": # noqa: A003\n \"\"\"Keys (and with values=True, values) starting with prefix.\"\"\"\n if _STATE_PATH:\n rows = sorted((k, r) for k, r in _local_state().items() if k.startswith(prefix))\n return [{\"key\": k, \"version\": r[\"version\"], **({\"value\": r[\"value\"]} if values else {})} for k, r in rows]\n keys: list = []\n after = None\n while True:\n params = {\"prefix\": prefix, \"values\": \"1\" if values else \"0\", \"limit\": 200, \"agent\": _agent()}\n if after:\n params[\"after\"] = after\n page = _request(\"GET\", f\"/api/agents/v1/state?{urllib.parse.urlencode(params)}\")\n for row in page[\"keys\"]:\n if \"value\" in row:\n row[\"value\"] = json.loads(row[\"value\"])\n keys.extend(page[\"keys\"])\n after = page.get(\"next\")\n if not after:\n return keys\n\n def update(self, key: str, fn, default: Any = None, retries: int = 10) -> Any:\n \"\"\"Read-modify-write that's safe with runs in parallel: fn(old) -> new,\n retried if another run wrote first. Returns the new value.\"\"\"\n for _ in range(retries):\n value, version = self.get_versioned(key)\n new = fn(default if value is None else value)\n try:\n self.set(key, new, if_version=version)\n return new\n except StateConflict:\n time.sleep(0.05)\n raise StateConflict(key, -1)\n\n def _write(self, key: str, value: Any, if_version: \"int | None\", ttl: \"float | None\") -> int:\n if _STATE_PATH:\n data = _local_state()\n current = data.get(key, {}).get(\"version\", 0)\n if if_version is not None and if_version != current:\n raise StateConflict(key, current)\n if value is None:\n data.pop(key, None)\n else:\n data[key] = {\"value\": value, \"version\": current + 1}\n with open(_STATE_PATH, \"w\") as f:\n json.dump(data, f, indent=2)\n return 0 if value is None else current + 1\n body: dict = {\"key\": key, \"value\": value, \"agent\": _agent()}\n if if_version is not None:\n body[\"ifVersion\"] = if_version\n if ttl:\n body[\"ttlSec\"] = int(ttl)\n res = _request(\"POST\", \"/api/agents/v1/state\", body, ok=(409,))\n if res.get(\"_status\") == 409:\n raise StateConflict(key, int(res.get(\"version\", 0)))\n return int(res.get(\"version\", 0))\n\n\ndef _agent() -> str:\n # Inside a run the run token picks the agent; a CLI token needs it named.\n return os.environ.get(\"CODIEDEV_AGENT\", \"\")\n\n\ndef _local_state() -> dict:\n try:\n with open(_STATE_PATH or \"\") as f:\n return json.load(f)\n except (FileNotFoundError, ValueError):\n return {}\n\n\nstate = _State()\n\n\ndef step(name: str, fn, ttl: float = 7 * 86400) -> Any:\n \"\"\"Run fn() once for this run and remember its (JSON) result: if the run\n is retried after a failure, finished steps return their saved result\n instead of running again. Use it around side effects (posting a reply,\n charging a card) so a retry doesn't repeat them.\"\"\"\n key = f\"step/{os.environ.get('CODIEDEV_RUN_ID', 'local')}/{name}\"\n value, version = state.get_versioned(key)\n if version:\n return value[\"result\"]\n result = fn()\n state.set(key, {\"result\": result}, ttl=ttl)\n return result\n\n\n_SELF = object()\n\n\ndef request_approval(\n title: str,\n details: Any = None,\n options: \"list[str] | None\" = None,\n approvers: \"list[str] | None\" = None,\n teams: \"list[str] | None\" = None,\n fields: \"list[dict] | None\" = None,\n then: Any = _SELF,\n context: Any = None,\n expires_in: \"float | None\" = None,\n) -> dict:\n \"\"\"Ask a person to decide. It shows in Codiedev (Agents → Approvals) and\n at the returned [\"url\"], which you can post anywhere (Slack, email).\n\n When it's decided, a run of `then` (this agent by default; None for no\n run) starts with input {\"approval\": {\"decision\", \"comment\", \"decidedBy\",\n ...}, \"context\": context}. If nobody decides within expires_in seconds\n (default 7 days), it starts with decision \"expired\". Usually the run\n ends after asking, so nothing runs (or costs) while it waits.\n options default to [\"approve\", \"reject\"]; approvers (emails) and teams\n (team names, e.g. [\"Finance\"]) limit who can decide (admins always can).\n Apps your company connects (Agents → Approvals → Apps) are told about it\n by webhook and can decide on a person's behalf.\n\n fields asks the person to fill something in as they decide, e.g.\n [{\"name\": \"rating\", \"type\": \"rating\"}, {\"name\": \"why\", \"type\": \"textarea\"}]\n (types: text, textarea, number, rating, select, multiselect, boolean;\n required, label, help, options, min, max, default). What they entered\n arrives as input[\"approval\"][\"values\"]. With fields and no options, the\n only choice is \"submit\".\"\"\"\n body: dict = {\"title\": title, \"details\": details, \"options\": options, \"approvers\": approvers, \"teams\": teams, \"fields\": fields, \"context\": context}\n if then is not _SELF:\n body[\"onDecision\"] = then\n if expires_in:\n body[\"expiresSec\"] = int(expires_in)\n if os.environ.get(\"CODIEDEV_LOCAL\") == \"1\":\n print(f\"[local] approval requested: {title} ({', '.join(options or (['submit'] if fields else ['approve', 'reject']))}); not sent on a local run\", flush=True)\n return {\"id\": f\"local-{int(time.time())}\", \"status\": \"pending\", \"url\": None, \"title\": title}\n return _request(\"POST\", \"/api/agents/v1/approvals\", {k: v for k, v in body.items() if v is not None or k == \"onDecision\"})\n\n\ndef get_approval(approval_id: str) -> dict:\n \"\"\"An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment.\"\"\"\n return _request(\"GET\", f\"/api/agents/v1/approvals?id={urllib.parse.quote(approval_id)}\")\n\n\ndef wait_for_approval(approval_id: str, timeout: float = 3600, poll: float = 15) -> dict:\n \"\"\"Wait until a person decides (or it expires), then return the approval. For agents that keep\n running while they wait, like a connected agent; a run inside Codiedev usually ends instead and\n is started again with the decision.\"\"\"\n deadline = time.time() + timeout\n while True:\n a = get_approval(approval_id)\n if a.get(\"status\") != \"pending\":\n return a\n if time.time() > deadline:\n raise TimeoutError(f\"approval {approval_id} still pending after {timeout}s\")\n time.sleep(poll)\n\n\nclass _Tasks:\n \"\"\"Projects & tasks, the same as `codiedev task …`. The agent acts as its\n owner (or whoever started the run) and what it writes says \"via <agent>\".\n Webhook events carry actor.agent, so skip your own when reacting to them.\"\"\"\n\n def _op(self, op: str, **body: Any) -> dict:\n return _request(\"POST\", \"/api/cli/work\", {\"op\": op, **{k: v for k, v in body.items() if v is not None}})\n\n def add(self, project: str, title: str = \"\", body: \"str | None\" = None, priority: \"str | None\" = None,\n state: \"str | None\" = None, from_ping: \"str | None\" = None, docs: \"list[str] | None\" = None,\n assignee: \"str | None\" = None) -> dict:\n \"\"\"New task in `project` (key like \"WEB\"); priority urgent|high|medium|low.\n from_ping makes it from a ping: the sender gets one reply when it's done.\n assignee (a teammate's name or email) hands it off: they get a ping.\"\"\"\n return self._op(\"task.add\", project=project, title=title, body=body, priority=priority, state=state,\n fromPingId=from_ping, artifactKeys=docs, assignee=assignee)\n\n def get(self, identifier: str) -> dict:\n \"\"\"Everything on a task: body, comments, PRs, sessions, people.\"\"\"\n return self._op(\"task.show\", identifier=identifier)[\"issue\"]\n\n def update(self, identifier: str, title: \"str | None\" = None, body: \"str | None\" = None, append: \"str | None\" = None,\n state: \"str | None\" = None, priority: \"str | None\" = None, assignee: \"str | None\" = None) -> dict:\n \"\"\"assignee: a teammate's name or email, \"me\", or \"none\".\"\"\"\n return self._op(\"task.update\", identifier=identifier, title=title, body=body, appendBody=append,\n state=state, priority=priority, assignee=assignee)\n\n def done(self, identifier: str) -> dict:\n return self.update(identifier, state=\"done\")\n\n def comment(self, identifier: str, body: str) -> dict:\n return self._op(\"task.comment\", identifier=identifier, body=body)\n\n def search(self, q: str, limit: \"int | None\" = None) -> list:\n return self._op(\"task.search\", q=q, limit=limit)[\"issues\"]\n\n def mine(self) -> list:\n \"\"\"Open tasks assigned to the agent's owner.\"\"\"\n return self._op(\"task.mine\")[\"issues\"]\n\n def next(self, project: str, limit: \"int | None\" = None) -> list:\n \"\"\"Open tasks in a project, most urgent first.\"\"\"\n return self._op(\"task.next\", project=project, limit=limit)[\"issues\"]\n\n def project(self, key: str) -> dict:\n \"\"\"A project with its tasks, sub-projects and people.\"\"\"\n return self._op(\"project.show\", project=key)[\"project\"]\n\n def projects(self) -> list:\n return self._op(\"project.list\")[\"projects\"]\n\n\ntasks = _Tasks()\n\n\ndef ping(to: str, body: str) -> dict:\n \"\"\"Message a teammate (name or email). It says which agent sent it and is\n emailed like any ping; an agent can ping each person 10 times an hour.\"\"\"\n return _request(\"POST\", \"/api/cli/work\", {\"op\": \"ping.send\", \"to\": to, \"body\": body})\n\n\ndef openai(**kwargs):\n \"\"\"An OpenAI client on the Codiedev gateway, with trace headers for this run.\"\"\"\n from openai import OpenAI\n\n headers = {\n \"X-Codiedev-Workload\": os.environ.get(\"CODIEDEV_AGENT\", \"local-agent\"),\n \"X-Codiedev-Run-Id\": os.environ.get(\"CODIEDEV_RUN_ID\", f\"local-{int(time.time())}\"),\n \"X-Codiedev-Environment\": \"production\" if os.environ.get(\"CODIEDEV_RUN_ID\") else \"development\",\n }\n headers.update(kwargs.pop(\"default_headers\", {}) or {})\n return OpenAI(\n base_url=os.environ.get(\"CODIEDEV_GATEWAY_URL\", \"https://gateway.codiedev.com/api/v1\"),\n api_key=os.environ.get(\"CODIEDEV_API_KEY\") or os.environ.get(\"CODIEDEV_TOKEN\"),\n default_headers=headers,\n **kwargs,\n )\n";
|
|
5
|
+
exports.jsIndex = "// Codiedev agents SDK for JavaScript and TypeScript, preinstalled in every\n// run. Same primitive as the Python SDK (codiedev_agents.py):\n//\n// import * as agents from \"codiedev-agents\";\n// const task = agents.input(); // this run's input (JSON)\n// const llm = await agents.openai(); // OpenAI client on the Codiedev gateway, traced\n// const child = await agents.run(\"summarize\", { text }); // another agent, as a child run\n// agents.output({ summary: child.output }); // this run's result\n// agents.respond({ text: \"on it\" }); // what a webhook caller waiting (?wait=) gets back\n// await agents.state.get(\"cursor\"); // per-agent storage that outlives runs\n// await agents.step(\"post\", () => post()); // done once per run, even across retries\n// await agents.start(\"triage\", input, { delaySec: 3600 }); // start a run later\n// await agents.requestApproval(\"Refund $120?\", { context }); // a person decides; a run acts on it\n//\n// Outside a run, set CODIEDEV_TOKEN and CODIEDEV_AGENTS_URL and call run() the same way.\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nconst INPUT_PATH = process.env.CODIEDEV_INPUT_PATH || \"/agent/.codiedev/input.json\";\nconst OUTPUT_PATH = process.env.CODIEDEV_OUTPUT_PATH || \"/agent/.codiedev/output.json\";\nconst REQUEST_PATH = process.env.CODIEDEV_REQUEST_PATH || \"/agent/.codiedev/request.json\";\nconst RESPONSE_PATH = process.env.CODIEDEV_RESPONSE_PATH || \"/agent/.codiedev/response.json\";\n// Set by `codiedev agents run --local`: state lives in this file instead of Codiedev.\nconst STATE_PATH = process.env.CODIEDEV_STATE_PATH;\n// Inside a run the run token picks the agent; a CLI token needs it named.\nconst AGENT = process.env.CODIEDEV_AGENT || \"\";\nconst DONE = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n\n/** state.set(..., { ifVersion }) found the key at another version. */\nexport class StateConflict extends Error {\n constructor(key, version) {\n super(`state ${JSON.stringify(key)} changed (now version ${version})`);\n this.key = key;\n this.version = version;\n }\n}\n\nexport class AgentRunError extends Error {\n constructor(run) {\n super(`${run.agent} run ${run.id} ${run.status}: ${run.error || \"no error message\"}`);\n this.run = run;\n }\n}\n\nfunction readJson(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch (err) {\n if (err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\n/** This run's input, parsed from JSON (`fallback` when started without one). */\nexport function input(fallback = null) {\n const value = readJson(INPUT_PATH);\n return value ?? fallback;\n}\n\n/** Set this run's result. Must be JSON-serializable; the last call wins. */\nexport function output(value) {\n fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });\n fs.writeFileSync(OUTPUT_PATH, JSON.stringify(value));\n}\n\n/** What a webhook caller gets back when it waits for the run (?wait=N): a\n * string is sent as text, anything else as JSON. Without it, a waiting\n * caller gets the run's output. */\nexport function respond(body = null, { status = 200, headers = {} } = {}) {\n fs.mkdirSync(path.dirname(RESPONSE_PATH), { recursive: true });\n fs.writeFileSync(RESPONSE_PATH, JSON.stringify({ status, headers, body }));\n}\n\n/** This run's attempt: 1, or 2+ when it's a retry of a failed attempt. */\nexport function attempt() {\n return Number(process.env.CODIEDEV_ATTEMPT || \"1\");\n}\n\n/** For a webhook run: { method, query, headers, body } with the raw body, to verify a signature. */\nexport function request() {\n return readJson(REQUEST_PATH);\n}\n\nfunction base() {\n const url = (process.env.CODIEDEV_AGENTS_URL || \"\").replace(/\\/$/, \"\");\n if (!url) throw new Error(\"Set CODIEDEV_AGENTS_URL (it's set for you inside a run)\");\n return url;\n}\n\nfunction token() {\n // Inside a run: the run's token. A connected agent (running outside Codiedev): its own key.\n // Otherwise: your CLI token.\n const key = process.env.CODIEDEV_API_KEY || \"\";\n const t = process.env.CODIEDEV_RUN_TOKEN || (key.startsWith(\"cdvm_\") ? key : \"\") || process.env.CODIEDEV_TOKEN;\n if (!t) throw new Error(\"Set CODIEDEV_API_KEY to your connected agent's key, or CODIEDEV_TOKEN to your CLI token (inside a run it's set for you)\");\n return t;\n}\n\nasync function call(method, route, body, ok = []) {\n const res = await fetch(`${base()}${route}`, {\n method,\n headers: { Authorization: `Bearer ${token()}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n const text = await res.text();\n let data = {};\n try {\n data = text ? JSON.parse(text) : {};\n } catch {\n data = { error: text };\n }\n if (ok.includes(res.status)) return { _status: res.status, ...data };\n if (!res.ok) throw new Error(`Codiedev agents API ${res.status}: ${data.error || text}`);\n return data;\n}\n\n/** Start a run and return its id without waiting. delaySec (up to 7 days)\n * starts it later; retries overrides the agent's retries. */\nexport async function start(agent, input = null, { idempotencyKey, delaySec, retries } = {}) {\n return (await call(\"POST\", \"/api/agents/v1/run\", {\n agent,\n input,\n ...(idempotencyKey ? { idempotencyKey } : {}),\n ...(delaySec ? { delaySec } : {}),\n ...(retries !== undefined ? { retries } : {}),\n })).runId;\n}\n\nfunction localState() {\n try {\n return JSON.parse(fs.readFileSync(STATE_PATH, \"utf8\"));\n } catch {\n return {};\n }\n}\n\nasync function writeState(key, value, { ifVersion, ttlSec } = {}) {\n if (STATE_PATH) {\n const data = localState();\n const current = data[key]?.version ?? 0;\n if (ifVersion !== undefined && ifVersion !== current) throw new StateConflict(key, current);\n if (value === null) delete data[key];\n else data[key] = { value, version: current + 1 };\n fs.writeFileSync(STATE_PATH, JSON.stringify(data, null, 2));\n return value === null ? 0 : current + 1;\n }\n const res = await call(\"POST\", \"/api/agents/v1/state\", { key, value, agent: AGENT, ...(ifVersion !== undefined ? { ifVersion } : {}), ...(ttlSec ? { ttlSec } : {}) }, [409]);\n if (res._status === 409) throw new StateConflict(key, res.version ?? 0);\n return res.version ?? 0;\n}\n\n/** Per-agent key-value storage that outlives runs (values: any JSON, up to\n * 64 KB; 1,000 keys). Every write bumps the key's version; pass ifVersion\n * to write only if nobody else did since you read it. */\nexport const state = {\n async get(key, fallback = null) {\n const { value } = await state.getVersioned(key);\n return value ?? fallback;\n },\n /** { value, version }; { value: null, version: 0 } when the key isn't set. */\n async getVersioned(key) {\n if (STATE_PATH) {\n const row = localState()[key];\n return row ? { value: row.value, version: row.version } : { value: null, version: 0 };\n }\n const res = await call(\"GET\", `/api/agents/v1/state?${new URLSearchParams({ key, agent: AGENT })}`, undefined, [404]);\n if (res._status === 404) return { value: null, version: 0 };\n return { value: JSON.parse(res.value), version: res.version };\n },\n /** Store value; returns the new version. Throws StateConflict on an ifVersion mismatch (0 = unset). */\n set(key, value, { ifVersion, ttlSec } = {}) {\n return writeState(key, value, { ifVersion, ttlSec });\n },\n async delete(key, { ifVersion } = {}) {\n await writeState(key, null, { ifVersion });\n },\n /** Keys (and with values: true, values) starting with prefix. */\n async list(prefix = \"\", { values = false } = {}) {\n if (STATE_PATH) {\n return Object.entries(localState())\n .filter(([k]) => k.startsWith(prefix))\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([key, row]) => ({ key, version: row.version, ...(values ? { value: row.value } : {}) }));\n }\n const out = [];\n let after = null;\n do {\n const q = new URLSearchParams({ prefix, values: values ? \"1\" : \"0\", limit: \"200\", agent: AGENT, ...(after ? { after } : {}) });\n const page = await call(\"GET\", `/api/agents/v1/state?${q}`);\n out.push(...page.keys.map((row) => (\"value\" in row ? { ...row, value: JSON.parse(row.value) } : row)));\n after = page.next;\n } while (after);\n return out;\n },\n /** Read-modify-write that's safe with runs in parallel: fn(old) => new, retried if another run wrote first. */\n async update(key, fn, fallback = null, retries = 10) {\n for (let i = 0; i < retries; i++) {\n const { value, version } = await state.getVersioned(key);\n const next = await fn(value ?? fallback);\n try {\n await state.set(key, next, { ifVersion: version });\n return next;\n } catch (err) {\n if (!(err instanceof StateConflict)) throw err;\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n }\n throw new StateConflict(key, -1);\n },\n};\n\n/** Run fn() once for this run and remember its (JSON) result: if the run is\n * retried after a failure, finished steps return their saved result instead\n * of running again. Use it around side effects so a retry doesn't repeat them. */\nexport async function step(name, fn, { ttlSec = 7 * 86400 } = {}) {\n const key = `step/${process.env.CODIEDEV_RUN_ID || \"local\"}/${name}`;\n const { value, version } = await state.getVersioned(key);\n if (version) return value.result;\n const result = await fn();\n await state.set(key, { result: result ?? null }, { ttlSec });\n return result;\n}\n\n/** Ask a person to decide. It shows in Codiedev (Agents → Approvals) and at\n * the returned .url, which you can post anywhere. When it's decided, a run of\n * `then` (this agent by default; null for none) starts with input\n * { approval: { decision, comment, decidedBy, ... }, context }. Undecided\n * after expiresSec (default 7 days): decision \"expired\". */\nexport async function requestApproval(title, { details, options, approvers, teams, fields, then, context, expiresSec } = {}) {\n if (process.env.CODIEDEV_LOCAL === \"1\") {\n console.log(`[local] approval requested: ${title} (${(options || (fields ? [\"submit\"] : [\"approve\", \"reject\"])).join(\", \")}); not sent on a local run`);\n return { id: `local-${Date.now()}`, status: \"pending\", url: null, title };\n }\n return call(\"POST\", \"/api/agents/v1/approvals\", {\n title,\n ...(details !== undefined ? { details } : {}),\n ...(options ? { options } : {}),\n ...(approvers ? { approvers } : {}),\n ...(teams ? { teams } : {}),\n ...(fields ? { fields } : {}),\n ...(then !== undefined ? { onDecision: then } : {}),\n ...(context !== undefined ? { context } : {}),\n ...(expiresSec ? { expiresSec } : {}),\n });\n}\n\n/** An approval's status (\"pending\", \"decided\", \"expired\"), decision and comment. */\nexport async function getApproval(id) {\n return call(\"GET\", `/api/agents/v1/approvals?id=${encodeURIComponent(id)}`);\n}\n\n/** Wait until a person decides (or it expires), then return the approval. For agents that keep\n * running while they wait, like a connected agent; a run inside Codiedev usually ends instead and\n * is started again with the decision. */\nexport async function waitForApproval(id, { timeoutMs = 3_600_000, pollMs = 15_000 } = {}) {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const a = await getApproval(id);\n if (a.status !== \"pending\") return a;\n if (Date.now() > deadline) throw new Error(`approval ${id} still pending after ${timeoutMs / 1000}s`);\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\n/** A run's status, input, output (parsed), error, log and children. */\nexport async function get(runId) {\n const run = await call(\"GET\", `/api/agents/v1/run?id=${encodeURIComponent(runId)}`);\n if (typeof run.output === \"string\") {\n try {\n run.output = JSON.parse(run.output);\n } catch {\n // not JSON: keep the string\n }\n }\n return run;\n}\n\n/** Run an agent and wait for it; its result is `.output`. Inside a run this\n * starts a child run. Throws AgentRunError if it fails unless check is false. */\nexport async function run(agent, input = null, { timeoutMs = 900_000, pollMs = 2000, check = true, idempotencyKey } = {}) {\n const id = await start(agent, input, { idempotencyKey });\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const r = await get(id);\n if (DONE.has(r.status)) {\n if (check && r.status !== \"succeeded\") throw new AgentRunError(r);\n return r;\n }\n if (Date.now() > deadline) throw new Error(`${agent} run ${id} still ${r.status} after ${timeoutMs / 1000}s`);\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\n/** Options for `new OpenAI(...)` on the Codiedev gateway, with this run's trace headers. */\nconst work = (op, body = {}) =>\n call(\"POST\", \"/api/cli/work\", { op, ...Object.fromEntries(Object.entries(body).filter(([, v]) => v !== undefined)) });\n\n/** Projects & tasks, the same as `codiedev task …`. The agent acts as its owner\n * and what it writes says \"via <agent>\". Webhook events carry actor.agent, so\n * skip your own when reacting to them. */\nexport const tasks = {\n add: (project, title = \"\", { body, priority, state, fromPing, docs, assignee } = {}) =>\n work(\"task.add\", { project, title, body, priority, state, fromPingId: fromPing, artifactKeys: docs, assignee }),\n get: async (identifier) => (await work(\"task.show\", { identifier })).issue,\n update: (identifier, { title, body, append, state, priority, assignee } = {}) =>\n work(\"task.update\", { identifier, title, body, appendBody: append, state, priority, assignee }),\n done: (identifier) => work(\"task.update\", { identifier, state: \"done\" }),\n comment: (identifier, body) => work(\"task.comment\", { identifier, body }),\n search: async (q, { limit } = {}) => (await work(\"task.search\", { q, limit })).issues,\n mine: async () => (await work(\"task.mine\")).issues,\n next: async (project, { limit } = {}) => (await work(\"task.next\", { project, limit })).issues,\n project: async (key) => (await work(\"project.show\", { project: key })).project,\n projects: async () => (await work(\"project.list\")).projects,\n};\n\n/** Message a teammate (name or email). It says which agent sent it and is\n * emailed like any ping; an agent can ping each person 10 times an hour. */\nexport function ping(to, body) {\n return work(\"ping.send\", { to, body });\n}\n\nexport function openaiOptions(extra = {}) {\n const inRun = !!process.env.CODIEDEV_RUN_ID;\n return {\n baseURL: process.env.CODIEDEV_GATEWAY_URL || \"https://gateway.codiedev.com/api/v1\",\n apiKey: process.env.CODIEDEV_API_KEY || process.env.CODIEDEV_TOKEN,\n ...extra,\n defaultHeaders: {\n \"X-Codiedev-Workload\": process.env.CODIEDEV_AGENT || \"local-agent\",\n \"X-Codiedev-Run-Id\": process.env.CODIEDEV_RUN_ID || `local-${Date.now()}`,\n \"X-Codiedev-Environment\": inRun ? \"production\" : \"development\",\n ...(extra.defaultHeaders || {}),\n },\n };\n}\n\n/** An OpenAI client on the Codiedev gateway (the `openai` package is preinstalled). */\nexport async function openai(extra = {}) {\n const { default: OpenAI } = await import(\"openai\");\n return new OpenAI(openaiOptions(extra));\n}\n";
|
|
6
|
+
exports.jsTypes = "/* eslint-disable @typescript-eslint/no-explicit-any -- defaults are `any` on purpose: agent code reads its own JSON input/state without declaring types first. */\nexport type RunStatus = \"queued\" | \"running\" | \"succeeded\" | \"failed\" | \"cancelled\";\nexport interface Run<O = unknown> {\n id: string;\n agent: string;\n version: number;\n status: RunStatus;\n trigger: \"cli\" | \"api\" | \"portal\" | \"schedule\" | \"webhook\" | \"child\" | \"approval\";\n parentRunId: string | null;\n rootRunId: string | null;\n depth: number;\n attempt: number;\n maxAttempts: number;\n scheduledFor: number | null;\n input: string | null;\n output: O;\n error: string | null;\n logTail: string | null;\n durationMs: number | null;\n children: Run[];\n}\nexport interface WebhookRequest { method: string; query: Record<string, string>; headers: Record<string, string>; body: string }\nexport interface Approval {\n id: string;\n agent: string;\n title: string;\n details: string | null;\n options: string[];\n status: \"pending\" | \"decided\" | \"expired\" | \"cancelled\";\n decision: string | null;\n comment: string | null;\n decidedBy: string | null;\n /** The approval app that sent the decision on decidedBy's behalf; null when decided in Codiedev. */\n decidedVia: string | null;\n approvers: string[];\n approverTeams: Array<{ id: string; name: string }>;\n /** The form the person fills in, if the approval asked for one. */\n fields: ApprovalField[];\n /** What they entered, by field name (null until decided). */\n values: Record<string, any> | null;\n url: string | null;\n resumeRunId: string | null;\n}\n/** One thing a person fills in when they decide. */\nexport interface ApprovalField {\n name: string;\n type: \"text\" | \"textarea\" | \"number\" | \"rating\" | \"select\" | \"multiselect\" | \"boolean\";\n label?: string;\n help?: string;\n required?: boolean;\n /** select / multiselect choices */\n options?: string[];\n /** number bounds; rating scale 1–max (default 5) */\n min?: number;\n max?: number;\n /** prefilled, e.g. a proposed amount for the person to correct */\n default?: unknown;\n}\n/** The input a run started by a decision gets. */\nexport interface ApprovalInput<C = any, V = Record<string, any>> {\n approval: { id: string; title: string; decision: string; comment: string | null; values: V; decidedBy: string | null; decidedVia: string | null; decidedAt: number; requestedByRun: string | null };\n context: C;\n}\nexport class AgentRunError extends Error { run: Run }\nexport class StateConflict extends Error { key: string; version: number }\nexport function input<T = any>(fallback?: T): T;\nexport function output(value: unknown): void;\nexport function request(): WebhookRequest | null;\nexport function respond(body?: unknown, opts?: { status?: number; headers?: Record<string, string> }): void;\nexport function attempt(): number;\nexport function start(agent: string, input?: unknown, opts?: { idempotencyKey?: string; delaySec?: number; retries?: number }): Promise<string>;\nexport function get<O = any>(runId: string): Promise<Run<O>>;\nexport function run<O = any>(agent: string, input?: unknown, opts?: { timeoutMs?: number; pollMs?: number; check?: boolean; idempotencyKey?: string }): Promise<Run<O>>;\nexport const state: {\n get<T = any>(key: string, fallback?: T): Promise<T>;\n getVersioned<T = any>(key: string): Promise<{ value: T | null; version: number }>;\n set(key: string, value: unknown, opts?: { ifVersion?: number; ttlSec?: number }): Promise<number>;\n delete(key: string, opts?: { ifVersion?: number }): Promise<void>;\n list(prefix?: string, opts?: { values?: boolean }): Promise<Array<{ key: string; version: number; value?: any }>>;\n update<T = any>(key: string, fn: (old: T) => T | Promise<T>, fallback?: T, retries?: number): Promise<T>;\n};\nexport function step<T>(name: string, fn: () => T | Promise<T>, opts?: { ttlSec?: number }): Promise<T>;\nexport function requestApproval<C = any>(title: string, opts?: { details?: unknown; options?: string[]; approvers?: string[]; teams?: string[]; fields?: ApprovalField[]; then?: string | null; context?: C; expiresSec?: number }): Promise<Approval>;\nexport function getApproval(id: string): Promise<Approval>;\nexport function waitForApproval(id: string, opts?: { timeoutMs?: number; pollMs?: number }): Promise<Approval>;\nexport function openaiOptions(extra?: Record<string, unknown>): { baseURL: string; apiKey: string | undefined; defaultHeaders: Record<string, string> };\nexport function openai(extra?: Record<string, unknown>): Promise<import(\"openai\").default>;\n\nexport type TaskRef = { identifier: string; title: string; state: string; priority: number; [k: string]: unknown };\nexport const tasks: {\n add(project: string, title?: string, opts?: { body?: string; priority?: \"urgent\" | \"high\" | \"medium\" | \"low\"; state?: string; fromPing?: string; docs?: string[]; assignee?: string }): Promise<{ identifier: string; [k: string]: unknown }>;\n get(identifier: string): Promise<Record<string, any>>;\n update(identifier: string, opts?: { title?: string; body?: string; append?: string; state?: string; priority?: string; assignee?: string }): Promise<Record<string, unknown>>;\n done(identifier: string): Promise<Record<string, unknown>>;\n comment(identifier: string, body: string): Promise<Record<string, unknown>>;\n search(q: string, opts?: { limit?: number }): Promise<TaskRef[]>;\n mine(): Promise<TaskRef[]>;\n next(project: string, opts?: { limit?: number }): Promise<TaskRef[]>;\n project(key: string): Promise<Record<string, any>>;\n projects(): Promise<Array<Record<string, any>>>;\n};\nexport function ping(to: string, body: string): Promise<{ pingId: string; recipient: { name: string; email: string } }>;\n";
|
|
7
7
|
exports.jsPackage = "{\n \"name\": \"codiedev-agents\",\n \"version\": \"0.1.0\",\n \"description\": \"Codiedev agents SDK: this run's input and output, chaining, and a traced OpenAI client.\",\n \"type\": \"module\",\n \"main\": \"./index.mjs\",\n \"types\": \"./index.d.ts\",\n \"exports\": { \".\": { \"types\": \"./index.d.ts\", \"default\": \"./index.mjs\" } }\n}\n";
|
package/dist/commands/inbox.js
CHANGED
|
@@ -105,7 +105,16 @@ async function runRead(args) {
|
|
|
105
105
|
console.log();
|
|
106
106
|
console.log(ping.body || "(empty)");
|
|
107
107
|
console.log();
|
|
108
|
+
if (ping.attachments?.length) {
|
|
109
|
+
console.log(ping.attachments.length === 1 ? "File:" : "Files:");
|
|
110
|
+
for (const a of ping.attachments) {
|
|
111
|
+
console.log(` ${(0, inboxFormat_1.fileList)([a])}`);
|
|
112
|
+
console.log(` ${a.url ?? "(no longer stored)"}`);
|
|
113
|
+
}
|
|
114
|
+
console.log();
|
|
115
|
+
}
|
|
108
116
|
console.log("✓ Marked as read");
|
|
117
|
+
console.log(`Reply: codiedev reply ${ping.pingId} "<message>"`);
|
|
109
118
|
if (ping.subjectKey) {
|
|
110
119
|
console.log(`Pull: codiedev pull ${ping.subjectKey}`);
|
|
111
120
|
}
|