@parcha/agentrun-kernel 0.0.0-stage → 0.1.0-beta.29

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +6 -0
  4. package/README.md +52 -2
  5. package/dist/delegate.d.ts +87 -0
  6. package/dist/delegate.js +143 -0
  7. package/dist/errors.d.ts +36 -0
  8. package/dist/errors.js +37 -0
  9. package/dist/host/alive.d.ts +2 -0
  10. package/dist/host/alive.js +28 -0
  11. package/dist/host/box-slot.d.ts +26 -0
  12. package/dist/host/box-slot.js +112 -0
  13. package/dist/host/box.d.ts +104 -0
  14. package/dist/host/box.js +16 -0
  15. package/dist/host/egress-proxy.d.ts +45 -0
  16. package/dist/host/egress-proxy.js +416 -0
  17. package/dist/host/egress-table.d.ts +24 -0
  18. package/dist/host/egress-table.js +165 -0
  19. package/dist/host/index.d.ts +16 -0
  20. package/dist/host/index.js +12 -0
  21. package/dist/host/overhead.d.ts +53 -0
  22. package/dist/host/overhead.js +75 -0
  23. package/dist/host/parent-death.d.ts +15 -0
  24. package/dist/host/parent-death.js +42 -0
  25. package/dist/host/parent-watch.d.ts +1 -0
  26. package/dist/host/parent-watch.js +38 -0
  27. package/dist/host/pinned-binary.d.ts +6 -0
  28. package/dist/host/pinned-binary.js +48 -0
  29. package/dist/host/proxy.d.ts +179 -0
  30. package/dist/host/proxy.js +629 -0
  31. package/dist/host/serial.d.ts +3 -0
  32. package/dist/host/serial.js +10 -0
  33. package/dist/index.d.ts +11 -0
  34. package/dist/index.js +6 -0
  35. package/dist/kernel/blobs.d.ts +16 -0
  36. package/dist/kernel/blobs.js +59 -0
  37. package/dist/kernel/drive.d.ts +35 -0
  38. package/dist/kernel/drive.js +202 -0
  39. package/dist/kernel/errors.d.ts +27 -0
  40. package/dist/kernel/errors.js +42 -0
  41. package/dist/kernel/follow-up.d.ts +67 -0
  42. package/dist/kernel/follow-up.js +146 -0
  43. package/dist/kernel/halt.d.ts +11 -0
  44. package/dist/kernel/halt.js +17 -0
  45. package/dist/kernel/index.d.ts +8 -0
  46. package/dist/kernel/index.js +5 -0
  47. package/dist/kernel/types.d.ts +120 -0
  48. package/dist/kernel/types.js +1 -0
  49. package/dist/process/builtins.d.ts +142 -0
  50. package/dist/process/builtins.js +207 -0
  51. package/dist/process/inbox.d.ts +29 -0
  52. package/dist/process/inbox.js +52 -0
  53. package/dist/process/index.d.ts +9 -0
  54. package/dist/process/index.js +11 -0
  55. package/dist/process/journal.d.ts +111 -0
  56. package/dist/process/journal.js +232 -0
  57. package/dist/process/records.d.ts +143 -0
  58. package/dist/process/records.js +142 -0
  59. package/dist/process/recovery.d.ts +76 -0
  60. package/dist/process/recovery.js +139 -0
  61. package/dist/process/settle.d.ts +11 -0
  62. package/dist/process/settle.js +20 -0
  63. package/dist/process/tail.d.ts +20 -0
  64. package/dist/process/tail.js +53 -0
  65. package/dist/process/tools.d.ts +105 -0
  66. package/dist/process/tools.js +120 -0
  67. package/dist/process/zod.d.ts +32 -0
  68. package/dist/process/zod.js +109 -0
  69. package/dist/runner.d.ts +154 -0
  70. package/dist/runner.js +547 -0
  71. package/dist/schema-check-cases.d.ts +7 -0
  72. package/dist/schema-check-cases.js +73 -0
  73. package/dist/schema-check.d.ts +3 -0
  74. package/dist/schema-check.js +88 -0
  75. package/dist/skill-files.d.ts +23 -0
  76. package/dist/skill-files.js +136 -0
  77. package/dist/testing.d.ts +91 -0
  78. package/dist/testing.js +196 -0
  79. package/dist/types.d.ts +237 -0
  80. package/dist/types.js +10 -0
  81. package/package.json +74 -4
  82. package/vendor/durable-harness/UPSTREAM.json +67 -0
@@ -0,0 +1,109 @@
1
+ // Copyright 2026 Vercel Inc. Licensed under the Apache License, Version 2.0; see LICENSE in this package.
2
+ // From github.com/vercel-labs/durable-harness, packages/harness-process/src/zod.ts at ea8aa4060c (UPSTREAM.json records its blob and sha256).
3
+ // Changed by Parcha, patches: 005-comments-name-the-agent-sdk
4
+ /**
5
+ * JSON Schema to zod, for harness SDKs whose tool APIs take zod (the Claude
6
+ * Agent SDK's in-process MCP tools, OpenCode's plugin tools). `HostTool`s carry
7
+ * JSON Schema; this converts the subset tool schemas use.
8
+ *
9
+ * zod is passed in (`import { z } from 'zod'`, v3 or v4) so this package
10
+ * does not pin one: use the one the harness SDK was built against.
11
+ *
12
+ * Adapted from the AI SDK's bridge (vercel/ai cbb6b49,
13
+ * `packages/harness-claude-code/src/bridge/json-schema-to-zod.ts`,
14
+ * Apache-2.0); changed to take `z` as a parameter and to export the shape.
15
+ */
16
+ /** A zod object for a JSON Schema object (extra properties allowed unless `additionalProperties: false`). */
17
+ export function zodFromJsonSchema(schema, z) {
18
+ return toZodObject(isSchema(schema) ? schema : {}, z);
19
+ }
20
+ /** The raw shape (`{ key: zodType }`) of a JSON Schema object, for APIs that take a shape. */
21
+ export function zodShapeFromJsonSchema(schema, z) {
22
+ return toZodShape(isSchema(schema) ? schema : undefined, z);
23
+ }
24
+ function toZodObject(schema, z) {
25
+ const object = z.object(toZodShape(schema, z));
26
+ if (schema.additionalProperties === false)
27
+ return object.strict();
28
+ // JSON Schema allows additional properties by default; zod strips them.
29
+ return object.catchall(isSchema(schema.additionalProperties) ? toZodType(schema.additionalProperties, z) : z.unknown());
30
+ }
31
+ function toZodShape(schema, z) {
32
+ if (!schema?.properties)
33
+ return {};
34
+ const required = new Set(schema.required);
35
+ const shape = {};
36
+ for (const [key, prop] of Object.entries(schema.properties)) {
37
+ let type = toZodType(prop, z);
38
+ if (!required.has(key))
39
+ type = type.optional();
40
+ if (prop.description)
41
+ type = type.describe(prop.description);
42
+ shape[key] = type;
43
+ }
44
+ return shape;
45
+ }
46
+ function toZodType(schema, z) {
47
+ if (!schema)
48
+ return z.any();
49
+ let type = forConst(schema, z) ?? forEnum(schema, z) ?? forUnion(schema, z) ?? forType(schema, z);
50
+ if (schema.nullable === true || (Array.isArray(schema.type) && schema.type.includes('null')))
51
+ type = type.nullable();
52
+ if (schema.description)
53
+ type = type.describe(schema.description);
54
+ return type;
55
+ }
56
+ function forType(schema, z) {
57
+ const types = Array.isArray(schema.type) ? schema.type.filter((t) => t !== 'null') : [schema.type].filter(Boolean);
58
+ if (types.length > 1)
59
+ return z.any();
60
+ if (Array.isArray(schema.type) && types.length === 0)
61
+ return z.null();
62
+ switch (types[0] ?? (schema.properties ? 'object' : undefined)) {
63
+ case 'string':
64
+ return z.string();
65
+ case 'number':
66
+ return z.number();
67
+ case 'integer':
68
+ return z.number().int();
69
+ case 'boolean':
70
+ return z.boolean();
71
+ case 'array':
72
+ return z.array(Array.isArray(schema.items) ? z.any() : toZodType(schema.items, z));
73
+ case 'object':
74
+ return toZodObject(schema, z);
75
+ case 'null':
76
+ return z.null();
77
+ default:
78
+ return z.any();
79
+ }
80
+ }
81
+ function forConst(schema, z) {
82
+ if (!('const' in schema))
83
+ return undefined;
84
+ return isLiteral(schema.const) ? z.literal(schema.const) : z.any();
85
+ }
86
+ function forEnum(schema, z) {
87
+ if (!Array.isArray(schema.enum))
88
+ return undefined;
89
+ if (!schema.enum.every(isLiteral))
90
+ return z.any();
91
+ const values = schema.enum;
92
+ if (values.length === 0)
93
+ return z.any();
94
+ if (values.length === 1)
95
+ return z.literal(values[0]);
96
+ return z.union(values.map((v) => z.literal(v)));
97
+ }
98
+ function forUnion(schema, z) {
99
+ const options = schema.anyOf ?? schema.oneOf;
100
+ if (!options || options.length < 2)
101
+ return undefined;
102
+ return z.union(options.map((o) => toZodType(o, z)));
103
+ }
104
+ function isSchema(input) {
105
+ return input != null && typeof input === 'object' && !Array.isArray(input);
106
+ }
107
+ function isLiteral(value) {
108
+ return value === null || typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value));
109
+ }
@@ -0,0 +1,154 @@
1
+ import type { AgentAdapter, HarnessEvent, HostTool, ModelPort, ModelUsage, NodeRequest, SessionRecord, SessionStore, SessionWriter } from "./types.js";
2
+ /** Exactly the interpreter's `runNode`, plus `stop`: a pause of every node it is running. With `drainMs`, each node
3
+ * starts no new turn, call or paid request, its calls in flight run on until their outcomes are recorded (for at most
4
+ * `drainMs`, on an adapter that can drain), and then it stops, failing `aborted`; its session resumes from its records
5
+ * when the node runs again. A node started after `stop` fails `aborted` at once. Resolves once every node has stopped. */
6
+ export type HarnessRunner = ((request: NodeRequest) => Promise<unknown>) & {
7
+ stop(options?: {
8
+ drainMs?: number;
9
+ }): Promise<void>;
10
+ };
11
+ /** The tool the runner injects; a host tool may not take its name. */
12
+ export declare const SUBMIT_TOOL = "submit";
13
+ /** The records the runner keeps beside the adapter's own, in each session. */
14
+ /** `request` is written before a paid request leaves, `turn` when it has answered with its usage: a request a crash cut off
15
+ * keeps its `request` and has no `turn`. */
16
+ export declare const RUNNER_RECORDS: {
17
+ readonly request: "runner.request";
18
+ readonly response: "runner.response";
19
+ readonly turn: "runner.turn";
20
+ readonly submit: "runner.submit";
21
+ readonly accepted: "runner.accepted";
22
+ };
23
+ /** What the host is asked before each paid request. A `refuse` ends the node, final. A `holdUsd` (a finite amount, 0
24
+ * or more) is what the host holds for the request, its cap's worst case: written on the request's record before it
25
+ * leaves, it is what the request counts at in the spend the host is shown when the request's own cost never comes in
26
+ * (a response cut short, booked at an unknown cost, or a request a crash cut off, read by a later process). Without
27
+ * one, such a request leaves the spend unknown. The cost booked for the request is unknown either way. */
28
+ export type BeforeRequest = (request: {
29
+ key: string;
30
+ label: string;
31
+ executionPath?: string;
32
+ model: string;
33
+ /** The turn this request would be, counting from 1; `maxTurns` is the node's limit. */
34
+ turn: number;
35
+ maxTurns: number | null;
36
+ /** The node's `budget_usd`, if it set one, and what its session has spent so far: a request whose cost is unknown at
37
+ * the figure its host held for it (`holdUsd`), and null when one has no such figure. */
38
+ budgetUsd?: number;
39
+ spentUsd: number | null;
40
+ /** The most input tokens this request can be billed for (the body's UTF-8 bytes plus what the provider adds, as the
41
+ * model proxy measured it), undefined when it was not measured: a budget treats that input as unknown, never 0. */
42
+ inputTokensMax?: number;
43
+ /** The most output tokens it asks for, and the least it may be limited to and still answer, as the model proxy read
44
+ * them from its body (ModelRequest). */
45
+ outputTokensAsked?: number;
46
+ outputTokensLeast?: number;
47
+ /** This session's paid requests admitted and not yet answered (in this process), oldest first, each with its own
48
+ * `inputTokensMax` and the output limit it was admitted with (`outputTokensMax`): a budget holds each one's
49
+ * worst case until it answers, since `spentUsd` counts answered requests only. Requests are admitted one at a time,
50
+ * so each check sees every request admitted before it. */
51
+ inFlight: readonly InFlight[];
52
+ }) => void | {
53
+ refuse: string;
54
+ } | Admission | Promise<void | {
55
+ refuse: string;
56
+ } | Admission>;
57
+ /** A request admitted and not yet answered, as a budget holds it. */
58
+ export type InFlight = {
59
+ turn: number;
60
+ model: string;
61
+ inputTokensMax?: number;
62
+ outputTokensMax?: number;
63
+ };
64
+ /** A host's admission: what it holds for the request (`holdUsd`), and the most output tokens, thinking included, it may
65
+ * be sent with (`outputTokensMax`, what is left of a cap can buy), which its ticket carries to the model proxy. A
66
+ * request its client stops part way (`ModelUsage.stoppedAfterBytes`), its cost unknown, counts at `stoppedUsd` of what
67
+ * streamed, in tokens (one a byte), never more than `holdUsd`; without it, at `holdUsd`. */
68
+ export type Admission = {
69
+ holdUsd?: number;
70
+ outputTokensMax?: number;
71
+ stoppedUsd?: (streamedTokens: number) => number;
72
+ };
73
+ export interface HarnessRunnerOptions {
74
+ /** The run's store, through whoever owns it. */
75
+ store: SessionStore;
76
+ /** The tools a node may name. A node that names none gets all of them; `tools: []` gets none. */
77
+ tools?: HostTool[];
78
+ /** The most turns (paid model requests) a node gets per attempt (default 8; null for no limit). A node's own
79
+ * `max_turns` applies within it; one that asks for more is refused, final. */
80
+ maxTurns?: number | null;
81
+ /** The most turns sent to a node per attempt when its turn limit is null (default 100). With a limit, sends are
82
+ * bounded by it instead. */
83
+ maxSends?: number;
84
+ /** The most `submit` calls a node gets per attempt (default 5; null for no limit). */
85
+ maxSubmissions?: number | null;
86
+ /** Each call's limit in milliseconds (default 120 s; null for none). */
87
+ timeoutMs?: number | null;
88
+ signal?: AbortSignal;
89
+ /** The host's check before each paid request: its budget, `max_cost_usd`. */
90
+ beforeRequest?: BeforeRequest;
91
+ /** Every event, with the node it belongs to. */
92
+ onEvent?: (event: HarnessEvent) => void;
93
+ /** The private directory of a session, when the adapter needs one. */
94
+ sessionDir?: (key: string) => string;
95
+ }
96
+ /** Turns in a row that may end with no paid request and no accepted submission before a node ends `no_progress`. The
97
+ * turn limit counts paid requests only, so without this bound a harness whose turns never reach its model (a cached or
98
+ * refused answer, a misconfigured model) would be sent turns forever, on microtasks no timer can interrupt. */
99
+ export declare const NO_PROGRESS_TURNS = 3;
100
+ /** What a paid request tells a host, beside its session's key and label. */
101
+ export type ModelPortEvent = {
102
+ type: "request_start";
103
+ turn: number;
104
+ model: string;
105
+ } | {
106
+ type: "request_end";
107
+ turn: number;
108
+ usage: ModelUsage;
109
+ };
110
+ export interface ModelPortOptions {
111
+ /** The session's key: what `beforeRequest` and every event carry. */
112
+ key: string;
113
+ label?: string;
114
+ executionPath?: string;
115
+ /** The most paid requests (turns) the session may make in this attempt; null (the default) for no limit. */
116
+ maxTurns?: number | null;
117
+ budgetUsd?: number;
118
+ /** Asked before every paid request; a `refuse` ends the attempt. */
119
+ beforeRequest?: BeforeRequest;
120
+ onEvent?: (event: ModelPortEvent & {
121
+ key: string;
122
+ label: string;
123
+ executionPath?: string;
124
+ }) => void;
125
+ /** The session's records, as already read (default: read from the writer). */
126
+ records?: readonly SessionRecord[];
127
+ /** How a record is written (default: the writer's append, which a newer open fences). */
128
+ append?: (records: SessionRecord[]) => Promise<void>;
129
+ /** The attempt has stopped: the error a new paid request is refused with. */
130
+ stopped?: () => Error | undefined;
131
+ /** A pause is draining the attempt: the error a new paid request is refused with, while the attempt itself goes on. A
132
+ * request is refused if the pause began before its record is written, even while the host's check of it was running. */
133
+ draining?: () => Error | undefined;
134
+ /** The port ends the attempt (the turn limit, the host's refusal, a cost it could not record): the error it throws. */
135
+ stop?: (reason: "turn_limit" | "refused" | "model_error", message?: string) => Error;
136
+ }
137
+ /** A session's booking of paid model requests: the adapter's model port, and what it has counted. */
138
+ export interface ModelBooking {
139
+ /** The port the adapter's session reports every paid request to (`SessionIO.models`). */
140
+ readonly models: ModelPort;
141
+ /** Paid requests that left in this session, one a crash cut off included. */
142
+ turns(): number;
143
+ /** What they cost; null once one's price is unknown. */
144
+ spentUsd(): number | null;
145
+ /** Books, once, each paid request a crash cut off (a `runner.request` with no `runner.turn`): a `runner.turn` marked
146
+ * `cut` at an unknown price, and a `request_end` saying so. A booked request is answered, so this is idempotent.
147
+ * Call it before the session sends anything. */
148
+ bookCut(): Promise<void>;
149
+ }
150
+ /** The runner's booking of paid model requests, for a host that drives an adapter session itself (a chat run): the same
151
+ * records (`runner.request` before a request leaves, `runner.turn` with its cost when it answers), the same turn limit
152
+ * and `beforeRequest`, the same events. `createHarnessRunner` is built on it. */
153
+ export declare function modelPortFor(writer: SessionWriter, options: ModelPortOptions): Promise<ModelBooking>;
154
+ export declare function createHarnessRunner(adapter: AgentAdapter, options: HarnessRunnerOptions): HarnessRunner;