@telnyx/agent-harness 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import type { AgentMessage, MergePatch, ScheduleOptions, SqlBindValue, SqlCursor, SqlValue, StoredEvent, StoredMessage, TaskRecord } from "@telnyx/edge-runtime/internal";
2
+ /** Exact major contract implemented by every COMPUTE-705 adapter. */
3
+ export declare const AGENT_HARNESS_PORTS_VERSION: 2;
4
+ /** Stable identity asserted by the host, never accepted from a harness input. */
5
+ export interface AgentHarnessIdentity {
6
+ readonly id: string;
7
+ }
8
+ /** Host clock used by queue and schedule operations. */
9
+ export interface AgentHarnessClock {
10
+ now(): number;
11
+ }
12
+ /** Gateway-attested Service Account identity. It is never caller or tool supplied. */
13
+ export interface AgentHarnessAuthorizationIdentity {
14
+ readonly serviceAccountId: string;
15
+ readonly organizationId: string;
16
+ }
17
+ /** Versioned private authorization capability required for approval decisions. */
18
+ export interface HarnessAuthorizationPort {
19
+ identity(): AgentHarnessAuthorizationIdentity | undefined;
20
+ authorize(request: Readonly<{
21
+ identity: AgentHarnessAuthorizationIdentity;
22
+ action: string;
23
+ resource: string;
24
+ runId: string;
25
+ approvalId: string;
26
+ }>): Promise<Readonly<{
27
+ kind: "allow" | "deny" | "unavailable";
28
+ }>>;
29
+ }
30
+ /** Existing Agent message-log semantics, without framework conversion helpers. */
31
+ export interface AgentHarnessMessagePort {
32
+ append(message: AgentMessage): Promise<number>;
33
+ appendMany(messages: AgentMessage[]): Promise<number>;
34
+ all(): Promise<StoredMessage[]>;
35
+ last(): Promise<StoredMessage | undefined>;
36
+ last(count: number): Promise<StoredMessage[]>;
37
+ count(): Promise<number>;
38
+ }
39
+ /**
40
+ * Existing durable Agent state semantics.
41
+ *
42
+ * `merge()` and `replace()` include the Agent's post-commit state-change hook.
43
+ * They may therefore reject after the new value is already durable; callers
44
+ * must read state before deciding whether a rejected mutation can be retried.
45
+ */
46
+ export interface AgentHarnessStatePort<State extends Record<string, unknown>> {
47
+ get(): Promise<State>;
48
+ merge(patch: MergePatch<State>): Promise<State>;
49
+ replace(state: State): Promise<State>;
50
+ }
51
+ /** Existing durable Agent EventLog semantics. */
52
+ export interface AgentHarnessEventPort {
53
+ emit(type: string, payload: unknown): Promise<number>;
54
+ read(afterSeq?: number, limit?: number): Promise<StoredEvent[]>;
55
+ count(): Promise<number>;
56
+ }
57
+ /**
58
+ * Existing Agent task scheduler semantics.
59
+ *
60
+ * Stable ids are pending-task upserts only. This is deliberately not a run
61
+ * admission ledger: there are no run statuses, terminal records, or durable
62
+ * idempotency history in this contract.
63
+ */
64
+ export interface AgentHarnessTaskPort {
65
+ readonly delivery: "at-least-once";
66
+ queue(method: string, payload?: unknown, options?: ScheduleOptions): Promise<string>;
67
+ schedule(delaySeconds: number, method: string, payload?: unknown, options?: ScheduleOptions): Promise<string>;
68
+ every(intervalSeconds: number, method: string, payload?: unknown, options?: ScheduleOptions): Promise<string>;
69
+ cancel(id: string): Promise<boolean>;
70
+ list(): Promise<TaskRecord[]>;
71
+ }
72
+ /** Synchronous per-actor SQL surface matching ActorStorage exactly. */
73
+ export interface AgentHarnessSqlPort {
74
+ exec<T extends Record<string, SqlValue> = Record<string, SqlValue>>(query: string, ...bindings: SqlBindValue[]): SqlCursor<T>;
75
+ transactionSync<T>(fn: () => T): T;
76
+ }
77
+ /**
78
+ * Versioned portable host contract for the execution harness.
79
+ *
80
+ * The environment is an explicitly selected, immutable capability bag. The
81
+ * raw Agent environment, actor context, and actor storage never cross this
82
+ * boundary.
83
+ */
84
+ export interface AgentHarnessPorts<State extends Record<string, unknown> = Record<string, unknown>> {
85
+ readonly version: typeof AGENT_HARNESS_PORTS_VERSION;
86
+ readonly identity: AgentHarnessIdentity;
87
+ /** Opaque host-owned token stable for one live actor activation. */
88
+ readonly activation: object;
89
+ readonly clock: AgentHarnessClock;
90
+ /** Omitted until a host supplies the production gateway-attested provider. Approval paths fail closed. */
91
+ readonly authorization?: HarnessAuthorizationPort;
92
+ readonly messages: AgentHarnessMessagePort;
93
+ readonly state: AgentHarnessStatePort<State>;
94
+ readonly events: AgentHarnessEventPort;
95
+ readonly tasks: AgentHarnessTaskPort;
96
+ readonly sql: AgentHarnessSqlPort;
97
+ readonly environment: Readonly<Record<string, unknown>>;
98
+ }
99
+ //# sourceMappingURL=ports.d.ts.map
package/dist/ports.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Exact major contract implemented by every COMPUTE-705 adapter. */
2
+ export const AGENT_HARNESS_PORTS_VERSION = 2;
3
+ //# sourceMappingURL=ports.js.map
@@ -0,0 +1,15 @@
1
+ import { type Env } from "@telnyx/edge-runtime/internal";
2
+ import { type AgentHarnessPorts } from "./ports.js";
3
+ /** Trusted construction options for selecting environment capabilities. */
4
+ export interface RuntimeAgentHarnessPortsOptions<E extends Env> {
5
+ /** Only these bindings cross into the portable ports object. */
6
+ environmentKeys?: readonly (keyof E & string)[];
7
+ }
8
+ /**
9
+ * Adapt an actual Agent through its runtime-owned symbol seam.
10
+ *
11
+ * The function never reflects over protected fields and never adds a
12
+ * string-named method to the Agent instance. All calls remain in-process.
13
+ */
14
+ export declare function createRuntimeAgentHarnessPorts<State extends Record<string, unknown>, E extends Env = Env>(agent: object, options?: RuntimeAgentHarnessPortsOptions<E>): AgentHarnessPorts<State>;
15
+ //# sourceMappingURL=runtime-adapter.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { getAgentHarnessHost, getAgentHarnessAuthorizationProvider, } from "@telnyx/edge-runtime/internal";
2
+ import { AGENT_HARNESS_PORTS_VERSION, } from "./ports.js";
3
+ const activations = new WeakMap();
4
+ function activationFor(agent) {
5
+ let activation = activations.get(agent);
6
+ if (activation === undefined) {
7
+ activation = Object.freeze({});
8
+ activations.set(agent, activation);
9
+ }
10
+ return activation;
11
+ }
12
+ /**
13
+ * Adapt an actual Agent through its runtime-owned symbol seam.
14
+ *
15
+ * The function never reflects over protected fields and never adds a
16
+ * string-named method to the Agent instance. All calls remain in-process.
17
+ */
18
+ export function createRuntimeAgentHarnessPorts(agent, options = {}) {
19
+ const host = getAgentHarnessHost(agent);
20
+ const authorization = getAgentHarnessAuthorizationProvider(agent);
21
+ const environment = host.selectEnvironment(options.environmentKeys ?? []);
22
+ const last = ((count) => count === undefined ? host.messages.last() : host.messages.last(count));
23
+ const messages = Object.freeze({
24
+ append: (message) => host.messages.append(message),
25
+ appendMany: (batch) => host.messages.appendMany(batch),
26
+ all: () => host.messages.all(),
27
+ last,
28
+ count: () => host.messages.count(),
29
+ });
30
+ const state = Object.freeze({
31
+ get: () => host.getState(),
32
+ merge: (patch) => host.mergeState(patch),
33
+ replace: (next) => host.replaceState(next),
34
+ });
35
+ const events = Object.freeze({
36
+ emit: (type, payload) => host.events.emit(type, payload),
37
+ read: (afterSeq, limit) => host.events.read(afterSeq, limit),
38
+ count: () => host.events.count(),
39
+ });
40
+ const tasks = Object.freeze({
41
+ delivery: "at-least-once",
42
+ queue: async (method, payload, scheduleOptions) => host.queue(method, payload, scheduleOptions),
43
+ schedule: async (delaySeconds, method, payload, scheduleOptions) => host.schedule(delaySeconds, method, payload, scheduleOptions),
44
+ every: async (intervalSeconds, method, payload, scheduleOptions) => host.every(intervalSeconds, method, payload, scheduleOptions),
45
+ cancel: (id) => host.cancelSchedule(id),
46
+ list: () => host.listSchedules(),
47
+ });
48
+ const ports = {
49
+ version: AGENT_HARNESS_PORTS_VERSION,
50
+ identity: Object.freeze({ id: host.id }),
51
+ activation: activationFor(agent),
52
+ clock: Object.freeze({ now: () => host.now() }),
53
+ ...(authorization === undefined ? {} : { authorization }),
54
+ messages,
55
+ state,
56
+ events,
57
+ tasks,
58
+ sql: Object.freeze({
59
+ exec(query, ...bindings) {
60
+ return host.sql.exec(query, ...bindings);
61
+ },
62
+ transactionSync(fn) {
63
+ return host.transactionSync(fn);
64
+ },
65
+ }),
66
+ environment,
67
+ };
68
+ return Object.freeze(ports);
69
+ }
70
+ //# sourceMappingURL=runtime-adapter.js.map
@@ -0,0 +1,38 @@
1
+ type NamedValues<T> = Readonly<Record<string, T>>;
2
+ type RuntimeTools = Readonly<Record<string, unknown>>;
3
+ /** Immutable, executable configuration selected for exactly one harness turn. */
4
+ export interface HarnessRuntimeConfig<Model = unknown, Tools extends RuntimeTools = RuntimeTools, Output = unknown> {
5
+ readonly model?: Model;
6
+ readonly instructions: string | undefined;
7
+ readonly tools?: Tools;
8
+ readonly limits: Readonly<{
9
+ steps: number;
10
+ }>;
11
+ readonly output?: Output;
12
+ }
13
+ /** Static executable configuration and immutable named registries owned by the host. */
14
+ export interface HarnessRuntimeConfigBase<Model = unknown, Tools extends RuntimeTools = RuntimeTools, Output = unknown> extends HarnessRuntimeConfig<Model, Tools, Output> {
15
+ readonly activeTools?: readonly string[];
16
+ readonly models?: NamedValues<Model>;
17
+ readonly outputs?: NamedValues<Output>;
18
+ }
19
+ export interface HarnessRuntimeOverride {
20
+ readonly model?: string | null;
21
+ readonly instructions?: string | null;
22
+ readonly tools?: readonly string[] | null;
23
+ readonly limits?: Readonly<{
24
+ steps?: number | null;
25
+ }> | null;
26
+ readonly output?: string | null;
27
+ }
28
+ export interface HarnessRuntimeResolution<Model = unknown, Tools extends RuntimeTools = RuntimeTools, Output = unknown> {
29
+ readonly valid: boolean;
30
+ readonly config: HarnessRuntimeConfig<Model, Tools, Output>;
31
+ }
32
+ /**
33
+ * Resolves the fixed `__telnyx_agent_harness` state value. Invalid candidates
34
+ * preserve the caller-provided immutable last valid value without partial merge.
35
+ */
36
+ export declare function resolveHarnessRuntimeConfig<Model, Tools extends RuntimeTools, Output>(base: HarnessRuntimeConfigBase<Model, Tools, Output>, overrideRoot: unknown, previous?: HarnessRuntimeConfig<Model, Tools, Output>): HarnessRuntimeResolution<Model, Tools, Output>;
37
+ export {};
38
+ //# sourceMappingURL=runtime-config.d.ts.map
@@ -0,0 +1,104 @@
1
+ import { z } from "zod/v4";
2
+ const maxStepsSchema = z.number().int().positive();
3
+ const configSchema = z
4
+ .object({
5
+ model: z.string().nullable().optional(),
6
+ instructions: z.string().nullable().optional(),
7
+ tools: z.array(z.string()).nullable().optional(),
8
+ limits: z.object({ steps: z.number().int().positive().nullable().optional() }).strict().nullable().optional(),
9
+ output: z.string().nullable().optional(),
10
+ })
11
+ .strict();
12
+ const rootSchema = z.object({ config: configSchema.nullable().optional() }).strict();
13
+ function frozen(value) {
14
+ return Object.freeze(value);
15
+ }
16
+ function own(value, key) {
17
+ return Object.prototype.hasOwnProperty.call(value, key);
18
+ }
19
+ function selectTools(tools, names) {
20
+ if (tools === undefined) {
21
+ if (names !== undefined && names.length > 0)
22
+ throw new TypeError("Harness runtime tools are unavailable");
23
+ return undefined;
24
+ }
25
+ const selectedNames = names ?? Object.keys(tools);
26
+ if (new Set(selectedNames).size !== selectedNames.length) {
27
+ throw new TypeError("Harness runtime tools cannot contain duplicate names");
28
+ }
29
+ const selected = {};
30
+ for (const name of selectedNames) {
31
+ if (!own(tools, name))
32
+ throw new TypeError(`Unknown harness runtime tool ${name}`);
33
+ selected[name] = tools[name];
34
+ }
35
+ return frozen(selected);
36
+ }
37
+ function staticConfig(base) {
38
+ const config = {
39
+ instructions: base.instructions,
40
+ limits: frozen({ steps: maxStepsSchema.parse(base.limits.steps) }),
41
+ ...(base.model === undefined ? {} : { model: base.model }),
42
+ ...(base.tools === undefined ? {} : { tools: selectTools(base.tools, base.activeTools) }),
43
+ ...(base.output === undefined ? {} : { output: base.output }),
44
+ };
45
+ return frozen(config);
46
+ }
47
+ /**
48
+ * Resolves the fixed `__telnyx_agent_harness` state value. Invalid candidates
49
+ * preserve the caller-provided immutable last valid value without partial merge.
50
+ */
51
+ export function resolveHarnessRuntimeConfig(base, overrideRoot, previous = staticConfig(base)) {
52
+ const staticValue = staticConfig(base);
53
+ if (overrideRoot === undefined || overrideRoot === null) {
54
+ return frozen({ valid: true, config: staticValue });
55
+ }
56
+ const root = rootSchema.safeParse(overrideRoot);
57
+ if (!root.success || root.data.config === null || root.data.config === undefined) {
58
+ return root.success
59
+ ? frozen({ valid: true, config: staticValue })
60
+ : frozen({ valid: false, config: previous });
61
+ }
62
+ const override = root.data.config;
63
+ try {
64
+ if (override.tools !== undefined && override.tools !== null && staticValue.tools === undefined) {
65
+ throw new TypeError("Harness runtime tools are unavailable");
66
+ }
67
+ const model = override.model === undefined || override.model === null
68
+ ? staticValue.model
69
+ : base.models !== undefined && own(base.models, override.model)
70
+ ? base.models[override.model]
71
+ : undefined;
72
+ if (override.model !== undefined && override.model !== null && model === undefined) {
73
+ throw new TypeError(`Unknown harness runtime model ${override.model}`);
74
+ }
75
+ const output = override.output === undefined || override.output === null
76
+ ? staticValue.output
77
+ : base.outputs !== undefined && own(base.outputs, override.output)
78
+ ? base.outputs[override.output]
79
+ : undefined;
80
+ if (override.output !== undefined && override.output !== null && output === undefined) {
81
+ throw new TypeError(`Unknown harness runtime output ${override.output}`);
82
+ }
83
+ const config = {
84
+ instructions: override.instructions === undefined || override.instructions === null
85
+ ? staticValue.instructions
86
+ : override.instructions,
87
+ limits: frozen({
88
+ steps: override.limits?.steps === undefined || override.limits?.steps === null
89
+ ? staticValue.limits.steps
90
+ : override.limits.steps,
91
+ }),
92
+ ...(model === undefined ? {} : { model }),
93
+ ...(staticValue.tools === undefined
94
+ ? {}
95
+ : { tools: selectTools(base.tools, override.tools === null ? base.activeTools : override.tools ?? base.activeTools) }),
96
+ ...(output === undefined ? {} : { output }),
97
+ };
98
+ return frozen({ valid: true, config: frozen(config) });
99
+ }
100
+ catch {
101
+ return frozen({ valid: false, config: previous });
102
+ }
103
+ }
104
+ //# sourceMappingURL=runtime-config.js.map
@@ -0,0 +1,46 @@
1
+ import { type ToolSet } from "ai";
2
+ import type { TaskRecord } from "@telnyx/edge-runtime/internal";
3
+ import { type HarnessRun, type HarnessRunLedger } from "./durable.js";
4
+ import { type Harness, type HarnessSpec } from "./harness.js";
5
+ import type { AgentHarnessPorts } from "./ports.js";
6
+ export type HarnessSchedule = Readonly<{
7
+ readonly id: string;
8
+ readonly mode: "delay" | "interval";
9
+ readonly nextFireAt: number;
10
+ readonly status: "active";
11
+ } & (Readonly<{
12
+ intervalSeconds?: never;
13
+ }> | Readonly<{
14
+ intervalSeconds: number;
15
+ }>)>;
16
+ export type HarnessScheduleRequest = Readonly<{
17
+ prompt: string;
18
+ delaySeconds?: number;
19
+ intervalSeconds?: number;
20
+ }>;
21
+ export interface HarnessScheduleCancellation {
22
+ readonly id: string;
23
+ readonly canceled: boolean;
24
+ }
25
+ export interface HarnessScheduling {
26
+ readonly tools: ToolSet;
27
+ schedule(input: HarnessScheduleRequest): Promise<Pick<HarnessSchedule, "id" | "mode" | "nextFireAt">>;
28
+ listSchedules(): Promise<readonly HarnessSchedule[]>;
29
+ cancelSchedule(id: string): Promise<HarnessScheduleCancellation>;
30
+ /** Dispatch a scheduler or harness admission task through durable ownership checks. */
31
+ dispatch(task: TaskRecord): Promise<HarnessRun | undefined>;
32
+ /** Reconcile durable schedule state with namespaced pending tasks after restart. */
33
+ recover(): Promise<number>;
34
+ }
35
+ /**
36
+ * Creates the bounded v0 model-facing schedule tools and the matching proactive
37
+ * task handler. Schedule definitions and occurrence-to-run correlation are
38
+ * private durable state; only active harness-owned schedules are projected.
39
+ */
40
+ export declare function createHarnessScheduling(ports: AgentHarnessPorts, ledger: HarnessRunLedger): HarnessScheduling;
41
+ /** Construct one durable harness with the bounded scheduling tools in its visible tool set. */
42
+ export declare function createHarnessWithScheduling<State extends Record<string, unknown>>(spec: HarnessSpec<State, ToolSet>): Readonly<{
43
+ harness: Harness<ToolSet>;
44
+ scheduling: HarnessScheduling;
45
+ }>;
46
+ //# sourceMappingURL=scheduling.d.ts.map