@dynamicagents/core 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +522 -0
- package/dist/a2a/agent-stub.d.ts +60 -0
- package/dist/a2a/agent-stub.js +1 -0
- package/dist/a2a/caller-token.d.ts +44 -0
- package/dist/a2a/caller-token.js +61 -0
- package/dist/a2a/caller.d.ts +23 -0
- package/dist/a2a/caller.js +33 -0
- package/dist/a2a/card.d.ts +158 -0
- package/dist/a2a/card.js +163 -0
- package/dist/a2a/context.d.ts +19 -0
- package/dist/a2a/context.js +61 -0
- package/dist/a2a/deliver.d.ts +159 -0
- package/dist/a2a/deliver.js +185 -0
- package/dist/a2a/executor.d.ts +84 -0
- package/dist/a2a/executor.js +106 -0
- package/dist/a2a/index.d.ts +35 -0
- package/dist/a2a/index.js +34 -0
- package/dist/a2a/notify.d.ts +106 -0
- package/dist/a2a/notify.js +180 -0
- package/dist/a2a/parts.d.ts +45 -0
- package/dist/a2a/parts.js +94 -0
- package/dist/a2a/push.d.ts +70 -0
- package/dist/a2a/push.js +53 -0
- package/dist/a2a/self-origin.d.ts +91 -0
- package/dist/a2a/self-origin.js +114 -0
- package/dist/a2a/task-store.d.ts +44 -0
- package/dist/a2a/task-store.js +99 -0
- package/dist/a2a/task.d.ts +85 -0
- package/dist/a2a/task.js +15 -0
- package/dist/a2a/verify.d.ts +80 -0
- package/dist/a2a/verify.js +143 -0
- package/dist/agent/budget.d.ts +46 -0
- package/dist/agent/budget.js +42 -0
- package/dist/agent/control.d.ts +109 -0
- package/dist/agent/control.js +115 -0
- package/dist/agent/errors.d.ts +85 -0
- package/dist/agent/errors.js +64 -0
- package/dist/agent/final-reply.d.ts +49 -0
- package/dist/agent/final-reply.js +68 -0
- package/dist/agent/history.d.ts +97 -0
- package/dist/agent/history.js +133 -0
- package/dist/agent/index.d.ts +29 -0
- package/dist/agent/index.js +29 -0
- package/dist/agent/inference.d.ts +110 -0
- package/dist/agent/inference.js +120 -0
- package/dist/agent/model.d.ts +90 -0
- package/dist/agent/model.js +1 -0
- package/dist/agent/session.d.ts +100 -0
- package/dist/agent/session.js +82 -0
- package/dist/agent/workers-ai/index.d.ts +23 -0
- package/dist/agent/workers-ai/index.js +23 -0
- package/dist/agent/workers-ai/runtime.d.ts +42 -0
- package/dist/agent/workers-ai/runtime.js +63 -0
- package/dist/alarm/index.d.ts +77 -0
- package/dist/alarm/index.js +116 -0
- package/dist/config.d.ts +202 -0
- package/dist/config.js +135 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +8 -0
- package/dist/contract/plugin.d.ts +324 -0
- package/dist/contract/plugin.js +114 -0
- package/dist/contract/recipe.d.ts +180 -0
- package/dist/contract/recipe.js +1 -0
- package/dist/contract/validation.d.ts +91 -0
- package/dist/contract/validation.js +84 -0
- package/dist/db/db.d.ts +147 -0
- package/dist/db/db.js +90 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.js +8 -0
- package/dist/db/migrations/index.d.ts +20 -0
- package/dist/db/migrations/index.js +60 -0
- package/dist/db/models/subtasks.d.ts +100 -0
- package/dist/db/models/subtasks.js +241 -0
- package/dist/db/models/tasks.d.ts +118 -0
- package/dist/db/models/tasks.js +274 -0
- package/dist/db/schema.d.ts +468 -0
- package/dist/db/schema.js +88 -0
- package/dist/env.d.ts +53 -0
- package/dist/env.js +47 -0
- package/dist/host/agent.d.ts +305 -0
- package/dist/host/agent.js +400 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/index.js +19 -0
- package/dist/host/plugin-host.d.ts +42 -0
- package/dist/host/plugin-host.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +17 -0
- package/dist/job/index.d.ts +21 -0
- package/dist/job/index.js +21 -0
- package/dist/job/lifecycle.d.ts +176 -0
- package/dist/job/lifecycle.js +230 -0
- package/dist/job/state.d.ts +92 -0
- package/dist/job/state.js +40 -0
- package/dist/platform.d.ts +138 -0
- package/dist/platform.js +140 -0
- package/dist/round/agent.d.ts +271 -0
- package/dist/round/agent.js +678 -0
- package/dist/round/index.d.ts +25 -0
- package/dist/round/index.js +23 -0
- package/dist/round/policy.d.ts +98 -0
- package/dist/round/policy.js +1 -0
- package/dist/round/subagent.d.ts +87 -0
- package/dist/round/subagent.js +108 -0
- package/dist/round/turn.d.ts +249 -0
- package/dist/round/turn.js +564 -0
- package/dist/round/workflow.d.ts +147 -0
- package/dist/round/workflow.js +387 -0
- package/dist/runtime/index.d.ts +127 -0
- package/dist/runtime/index.js +186 -0
- package/dist/runtime/tool-families.d.ts +32 -0
- package/dist/runtime/tool-families.js +61 -0
- package/dist/subagent/fingerprint.d.ts +37 -0
- package/dist/subagent/fingerprint.js +92 -0
- package/dist/subagent/index.d.ts +169 -0
- package/dist/subagent/index.js +330 -0
- package/dist/subagent/prompt.d.ts +32 -0
- package/dist/subagent/prompt.js +37 -0
- package/dist/subagent/run.d.ts +157 -0
- package/dist/subagent/run.js +540 -0
- package/dist/subagent/workspace.d.ts +85 -0
- package/dist/subagent/workspace.js +127 -0
- package/dist/subtasks/catalog.d.ts +40 -0
- package/dist/subtasks/catalog.js +36 -0
- package/dist/subtasks/decomposition.d.ts +85 -0
- package/dist/subtasks/decomposition.js +156 -0
- package/dist/subtasks/delegate.d.ts +120 -0
- package/dist/subtasks/delegate.js +131 -0
- package/dist/subtasks/index.d.ts +9 -0
- package/dist/subtasks/index.js +9 -0
- package/dist/subtasks/subtask-types.d.ts +91 -0
- package/dist/subtasks/subtask-types.js +103 -0
- package/dist/subtasks/types.d.ts +295 -0
- package/dist/subtasks/types.js +15 -0
- package/dist/testing/auth.d.ts +34 -0
- package/dist/testing/auth.js +35 -0
- package/dist/testing/do.d.ts +29 -0
- package/dist/testing/do.js +25 -0
- package/dist/testing/fake-session.d.ts +26 -0
- package/dist/testing/fake-session.js +37 -0
- package/dist/testing/fixtures.d.ts +64 -0
- package/dist/testing/fixtures.js +104 -0
- package/dist/testing/harness.d.ts +97 -0
- package/dist/testing/harness.js +138 -0
- package/dist/testing/index.d.ts +31 -0
- package/dist/testing/index.js +35 -0
- package/dist/testing/mock-model.d.ts +77 -0
- package/dist/testing/mock-model.js +136 -0
- package/dist/testing/node.d.ts +56 -0
- package/dist/testing/node.js +56 -0
- package/dist/testing/vcr-global-setup.d.ts +12 -0
- package/dist/testing/vcr-global-setup.js +15 -0
- package/dist/testing/vcr-shared.d.ts +38 -0
- package/dist/testing/vcr-shared.js +33 -0
- package/dist/testing/vcr-spec.d.ts +25 -0
- package/dist/testing/vcr-spec.js +124 -0
- package/dist/testing/vcr-store.d.ts +86 -0
- package/dist/testing/vcr-store.js +191 -0
- package/dist/testing/vcr.d.ts +117 -0
- package/dist/testing/vcr.js +275 -0
- package/dist/worker/define-agent.d.ts +123 -0
- package/dist/worker/define-agent.js +20 -0
- package/dist/worker/index.d.ts +218 -0
- package/dist/worker/index.js +369 -0
- package/eslint-rules/index.js +31 -0
- package/eslint-rules/no-deprecated-object-properties.js +81 -0
- package/package.json +178 -0
- package/scripts/generate-keys.mjs +48 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { makeDecompositionProposalSchema } from "./decomposition.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `delegate` tool — the single act by which the main agent hands work to
|
|
5
|
+
* subagents, and the shape both ends of that act agree on.
|
|
6
|
+
*
|
|
7
|
+
* Delegation is a **durable tool call**: a round emits it for real (the model
|
|
8
|
+
* picks it and fills its input), the Workflow performs it over minutes or hours,
|
|
9
|
+
* and a later round reassembles the call with its result to keep working. The two
|
|
10
|
+
* halves are separated by a Workflow boundary, not by a fiction — nothing here is
|
|
11
|
+
* fabricated on the model's behalf.
|
|
12
|
+
*
|
|
13
|
+
* This module owns the tool's identity because every round depends on it
|
|
14
|
+
* agreeing: a later round pairs a synthesized `tool-result` to an earlier round's
|
|
15
|
+
* `tool-call` by name and id, and a mismatch would not throw — it would silently
|
|
16
|
+
* produce a malformed history that the model quietly misreads.
|
|
17
|
+
*/
|
|
18
|
+
export const DELEGATE_TOOL_NAME = "delegate";
|
|
19
|
+
/**
|
|
20
|
+
* The tool as the model sees it. Deliberately **without `execute`**: the Workflow
|
|
21
|
+
* performs this call, durably, outside the inference — so there is nothing for the
|
|
22
|
+
* SDK to run, and the tool loop halts on the call rather than trying to continue
|
|
23
|
+
* past it. That is what makes it a *control* tool: unlike the agent's work tools
|
|
24
|
+
* (`set_context`, and whatever the installed plugins offer), calling it ends the
|
|
25
|
+
* round.
|
|
26
|
+
*
|
|
27
|
+
* Its `inputSchema` is the delegation contract itself, and it is the **only**
|
|
28
|
+
* declaration of this tool. One schema has to serve both directions — the calls
|
|
29
|
+
* the model emits now, and the calls reconstructed from durable rows in later
|
|
30
|
+
* rounds — because a provider cannot be shown two shapes for one tool name in a
|
|
31
|
+
* single request. See {@link delegateCallInput} for how a durable row is rendered
|
|
32
|
+
* back into it.
|
|
33
|
+
*
|
|
34
|
+
* Built per registry rather than declared as a module constant: both the type
|
|
35
|
+
* enum in its schema and the catalogue in its description are facts about which
|
|
36
|
+
* plugins are installed, and in the predecessor repo both were frozen at import
|
|
37
|
+
* time — which is exactly what made the type set unoverridable.
|
|
38
|
+
*/
|
|
39
|
+
export function makeDelegateTool(types, maxSubtasks) {
|
|
40
|
+
return tool({
|
|
41
|
+
description: "Delegate part of the user's request to isolated subagents and acknowledge it. Their results return to you, and you then decide what to do next — answer the user, or delegate again.\n\n" +
|
|
42
|
+
"Every subtask must name one of these types, and supply the params that type requires:\n" +
|
|
43
|
+
types.renderTypes(),
|
|
44
|
+
inputSchema: makeDecompositionProposalSchema(types, maxSubtasks)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The call's id, derived from the parent Task and the round that emitted it —
|
|
49
|
+
* deterministic and replay-safe, the same discipline as the Session message ids
|
|
50
|
+
* it sits alongside (see {@link file://../agent/history.ts}). Later rounds rebuild it
|
|
51
|
+
* rather than storing it.
|
|
52
|
+
*
|
|
53
|
+
* **Underscores, not colons, and this is load-bearing.** Unlike a Session message
|
|
54
|
+
* id, this one is sent to a provider as a `tool_use.id`, and Anthropic validates
|
|
55
|
+
* that field against `^[a-zA-Z0-9_-]+$`. The colon-separated form this used to
|
|
56
|
+
* return failed every round from the first delegation onwards — round 0 was fine
|
|
57
|
+
* because the model authors its own ids, and round 1 reconstructs this one, so
|
|
58
|
+
* the request 400d deterministically on both the primary and the fallback until
|
|
59
|
+
* the deterministic join fired. Workers AI never validated the field, which is
|
|
60
|
+
* why it took a Claude-backed agent to surface it.
|
|
61
|
+
*
|
|
62
|
+
* Nothing persists this: both halves of the pair are rebuilt together on every
|
|
63
|
+
* request, so changing the shape needs no migration. There is no longer a
|
|
64
|
+
* provider-side backstop in core — the adapter that carried one went with
|
|
65
|
+
* `./anthropic` in 0.8.0 — so a provider added here that validates tool-call ids
|
|
66
|
+
* needs to sanitize them on its own way out.
|
|
67
|
+
*/
|
|
68
|
+
export function delegateToolCallId(taskId, round) {
|
|
69
|
+
return `task_${taskId}_round_${round}_delegate`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Ceiling on one branch's `output`, applied to both halves of it.
|
|
73
|
+
*
|
|
74
|
+
* A round's history holds every branch of every earlier round, so this is
|
|
75
|
+
* multiplied by the whole delegation history rather than paid once. Generous
|
|
76
|
+
* enough for a report a subagent meant to be read, far short of a build log a
|
|
77
|
+
* failing one dumped into `error`.
|
|
78
|
+
*/
|
|
79
|
+
const MAX_OUTPUT_CHARS = 8_000;
|
|
80
|
+
function bounded(text) {
|
|
81
|
+
if (text.length <= MAX_OUTPUT_CHARS)
|
|
82
|
+
return text;
|
|
83
|
+
const suffix = "\n…[truncated]";
|
|
84
|
+
return `${text.slice(0, MAX_OUTPUT_CHARS - suffix.length)}${suffix}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Rebuild one round's call input from its durable rows, in stable ordinal order.
|
|
88
|
+
* Typed as {@link DecompositionProposal} — the same type the model's own calls
|
|
89
|
+
* are validated into — so the reconstructed call and an emitted one cannot drift
|
|
90
|
+
* apart in shape.
|
|
91
|
+
*
|
|
92
|
+
* `referenceIndexes` is omitted rather than faked: this round's references were
|
|
93
|
+
* snapshotted verbatim onto the rows when it ran, and the catalog they were
|
|
94
|
+
* chosen from is long gone.
|
|
95
|
+
*
|
|
96
|
+
* A subtask proposal carries no identifier of its own, so the model pairs each
|
|
97
|
+
* entry here with its outcome **by position**: both arrays are built from the
|
|
98
|
+
* same ordinal-ordered `branches`, and the outcome additionally repeats `type`
|
|
99
|
+
* and carries the durable `subtaskId`.
|
|
100
|
+
*/
|
|
101
|
+
export function delegateCallInput(reply, branches) {
|
|
102
|
+
return {
|
|
103
|
+
reply,
|
|
104
|
+
subtasks: branches.map((branch) => ({
|
|
105
|
+
type: branch.type,
|
|
106
|
+
prompt: branch.prompt,
|
|
107
|
+
// Reconstructed verbatim from the row: a later round must see the same
|
|
108
|
+
// params the call really carried, or it cannot reason about what ran.
|
|
109
|
+
params: branch.params
|
|
110
|
+
}))
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Rebuild one round's call result from its durable rows, in stable ordinal order.
|
|
115
|
+
*
|
|
116
|
+
* A completed branch reports its parts; any other branch reports its `error`, or
|
|
117
|
+
* `null` when it has none to give — a cancelled branch usually does not, and
|
|
118
|
+
* inventing a sentence for it would be worse than the absence.
|
|
119
|
+
*/
|
|
120
|
+
export function delegateCallOutput(branches) {
|
|
121
|
+
return branches.map((branch) => ({
|
|
122
|
+
subtaskId: branch.subtaskId,
|
|
123
|
+
type: branch.type,
|
|
124
|
+
status: branch.status,
|
|
125
|
+
output: branch.status === "completed"
|
|
126
|
+
? bounded((branch.resultParts ?? []).map((part) => part.text).join("\n"))
|
|
127
|
+
: branch.error
|
|
128
|
+
? bounded(branch.error)
|
|
129
|
+
: null
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/subtasks` — the delegation layer: what a round may hand out,
|
|
3
|
+
* and how a proposal becomes durable drafts.
|
|
4
|
+
*/
|
|
5
|
+
export { makeSubtaskTypes, SubtaskParamsError, type SubtaskTypeRegistry } from "./subtask-types.js";
|
|
6
|
+
export { DecompositionValidationError, makeDecompositionProposalSchema, nonBlank, resolveDecomposition } from "./decomposition.js";
|
|
7
|
+
export { DELEGATE_TOOL_NAME, makeDelegateTool, delegateToolCallId, delegateCallInput, delegateCallOutput, type DelegateSubtaskOutcome } from "./delegate.js";
|
|
8
|
+
export { isCatalogEligible, type ReferenceCatalogEntry } from "./catalog.js";
|
|
9
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dynamicagents/core/subtasks` — the delegation layer: what a round may hand out,
|
|
3
|
+
* and how a proposal becomes durable drafts.
|
|
4
|
+
*/
|
|
5
|
+
export { makeSubtaskTypes, SubtaskParamsError } from "./subtask-types.js";
|
|
6
|
+
export { DecompositionValidationError, makeDecompositionProposalSchema, nonBlank, resolveDecomposition } from "./decomposition.js";
|
|
7
|
+
export { DELEGATE_TOOL_NAME, makeDelegateTool, delegateToolCallId, delegateCallInput, delegateCallOutput } from "./delegate.js";
|
|
8
|
+
export { isCatalogEligible } from "./catalog.js";
|
|
9
|
+
export * from "./types.js";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DelegationNames, ResolvedRecipe, SubtaskParams, SubtaskTypeSpec } from "../contract/recipe.js";
|
|
3
|
+
/**
|
|
4
|
+
* The **closed set of subtask types** the main agent may delegate, as the
|
|
5
|
+
* runtime uses it: lookup, the delegate enum, param validation, and the
|
|
6
|
+
* catalogue the delegating model is shown.
|
|
7
|
+
*
|
|
8
|
+
* The set is not declared here, and — unlike the predecessor repo — it is not a
|
|
9
|
+
* module constant. There, `SUBTASK_TYPE_SPECS` was imported at module scope and
|
|
10
|
+
* every derived value (`SUBTASK_TYPES`, `SUBTASK_TYPE_KEYS`, the delegate tool's
|
|
11
|
+
* description, the round contract) was computed at *import time*. That made the
|
|
12
|
+
* registry unoverridable, froze it before `env` exists, and pulled every
|
|
13
|
+
* domain's module into the bundle whether or not the agent used it.
|
|
14
|
+
*
|
|
15
|
+
* So it is a factory. The host builds one registry per DO instance from its
|
|
16
|
+
* installed plugins, and everything downstream reads that object.
|
|
17
|
+
*/
|
|
18
|
+
export type { DelegationNames, SubtaskParams, SubtaskTypeSpec };
|
|
19
|
+
/** A subtask whose params do not satisfy its type's contract. */
|
|
20
|
+
export declare class SubtaskParamsError extends Error {
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
export interface SubtaskTypeRegistry {
|
|
24
|
+
/** Every known spec, in declaration order. */
|
|
25
|
+
readonly specs: readonly SubtaskTypeSpec[];
|
|
26
|
+
/** Every known type, keyed by its `key`. */
|
|
27
|
+
readonly byKey: ReadonlyMap<string, SubtaskTypeSpec>;
|
|
28
|
+
/** The type keys in declaration order. Empty when the agent delegates nothing. */
|
|
29
|
+
readonly keys: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* The keys as a non-empty tuple for `z.enum` in the delegate schema — the
|
|
32
|
+
* model cannot emit anything outside it, so an unknown type never reaches the
|
|
33
|
+
* data layer.
|
|
34
|
+
*
|
|
35
|
+
* Throws when no type is registered. Not a defensive check but the real
|
|
36
|
+
* contract: an agent with no subtask types has nothing to delegate to, and
|
|
37
|
+
* must not offer a delegate tool at all.
|
|
38
|
+
*/
|
|
39
|
+
enumKeys(): [string, ...string[]];
|
|
40
|
+
/** Look up a type, or null when it is not one we know. */
|
|
41
|
+
spec(type: string): SubtaskTypeSpec | null;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a subtask type to the recipe it runs under. Throws
|
|
44
|
+
* {@link SubtaskParamsError} for an unknown or retired type — the delegate
|
|
45
|
+
* enum prevents the model from emitting one, so any unknown type reaching
|
|
46
|
+
* execution is a configuration bug and should fail terminally.
|
|
47
|
+
*/
|
|
48
|
+
resolveRecipe(type: string): ResolvedRecipe;
|
|
49
|
+
/**
|
|
50
|
+
* Validate a subtask's params against its type's contract, returning the
|
|
51
|
+
* params to persist. A type with no schema must carry no params — a stray
|
|
52
|
+
* param would otherwise ride along unread, looking meaningful.
|
|
53
|
+
*
|
|
54
|
+
* Shape only: whether an id names something the outside world actually offers
|
|
55
|
+
* is a question for that world, answered when the work starts. Unknown keys
|
|
56
|
+
* are stripped rather than refused — a model that invents one has produced a
|
|
57
|
+
* valid subtask with noise attached, and the noise provably cannot reach the
|
|
58
|
+
* execution.
|
|
59
|
+
*/
|
|
60
|
+
validateParams(type: string, params: SubtaskParams | undefined): SubtaskParams;
|
|
61
|
+
/**
|
|
62
|
+
* Every param key any type declares, as the optional properties of the single
|
|
63
|
+
* flat `params` object the delegate tool advertises.
|
|
64
|
+
*
|
|
65
|
+
* Optional is not a weakening: which keys a given type *requires* is
|
|
66
|
+
* {@link validateParams}'s call, and it still refuses a subtask whose type's
|
|
67
|
+
* params are missing. What this adds is **visibility**. One tool schema has to
|
|
68
|
+
* serve every type, so the only alternative to naming the union of keys here
|
|
69
|
+
* is naming none of them — and a key the schema does not name is a key the
|
|
70
|
+
* model has no legal way to send, whatever the description promises. That is
|
|
71
|
+
* exactly how a domain subtask once reached validation with none of its
|
|
72
|
+
* declared ids: the field was declared as a free-form record, which the
|
|
73
|
+
* provider's schema conversion flattens to an object permitting no keys at all.
|
|
74
|
+
*
|
|
75
|
+
* Descriptions are prefixed with the owning type, so a flat namespace still
|
|
76
|
+
* reads unambiguously to the model (and a key two types share names both).
|
|
77
|
+
*/
|
|
78
|
+
paramProperties(): Record<string, z.ZodType<string | undefined>>;
|
|
79
|
+
/** The type catalogue as the delegating model is shown it. */
|
|
80
|
+
renderTypes(): string;
|
|
81
|
+
/** Every type's {@link SubtaskTypeSpec.delegationGuidance}, for the round contract. */
|
|
82
|
+
renderDelegationGuidance(names: DelegationNames): string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the registry from the specs the installed plugins declared.
|
|
86
|
+
*
|
|
87
|
+
* Duplicate keys are refused rather than last-one-wins: two plugins claiming one
|
|
88
|
+
* type is a misconfiguration whose symptom would otherwise be work silently
|
|
89
|
+
* routed to the wrong domain.
|
|
90
|
+
*/
|
|
91
|
+
export declare function makeSubtaskTypes(specs: readonly SubtaskTypeSpec[]): SubtaskTypeRegistry;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** A subtask whose params do not satisfy its type's contract. */
|
|
3
|
+
export class SubtaskParamsError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "SubtaskParamsError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build the registry from the specs the installed plugins declared.
|
|
11
|
+
*
|
|
12
|
+
* Duplicate keys are refused rather than last-one-wins: two plugins claiming one
|
|
13
|
+
* type is a misconfiguration whose symptom would otherwise be work silently
|
|
14
|
+
* routed to the wrong domain.
|
|
15
|
+
*/
|
|
16
|
+
export function makeSubtaskTypes(specs) {
|
|
17
|
+
const byKey = new Map();
|
|
18
|
+
for (const spec of specs) {
|
|
19
|
+
if (byKey.has(spec.key)) {
|
|
20
|
+
throw new Error(`duplicate subtask type "${spec.key}" — two plugins declare the same type`);
|
|
21
|
+
}
|
|
22
|
+
byKey.set(spec.key, spec);
|
|
23
|
+
}
|
|
24
|
+
const keys = specs.map((s) => s.key);
|
|
25
|
+
const lookup = (type) => byKey.get(type) ?? null;
|
|
26
|
+
return {
|
|
27
|
+
specs,
|
|
28
|
+
byKey,
|
|
29
|
+
keys,
|
|
30
|
+
enumKeys() {
|
|
31
|
+
if (keys.length === 0) {
|
|
32
|
+
throw new Error("no subtask types are registered — an agent with no types cannot delegate, " +
|
|
33
|
+
"so it must not build a delegate tool");
|
|
34
|
+
}
|
|
35
|
+
return keys;
|
|
36
|
+
},
|
|
37
|
+
spec: lookup,
|
|
38
|
+
resolveRecipe(type) {
|
|
39
|
+
const found = lookup(type);
|
|
40
|
+
if (!found)
|
|
41
|
+
throw new SubtaskParamsError(`unknown subtask type: ${type}`);
|
|
42
|
+
return found.recipe;
|
|
43
|
+
},
|
|
44
|
+
validateParams(type, params) {
|
|
45
|
+
const found = lookup(type);
|
|
46
|
+
if (!found)
|
|
47
|
+
throw new SubtaskParamsError(`unknown subtask type: ${type}`);
|
|
48
|
+
const supplied = params ?? {};
|
|
49
|
+
if (!found.params) {
|
|
50
|
+
const extra = Object.keys(supplied);
|
|
51
|
+
if (extra.length > 0) {
|
|
52
|
+
throw new SubtaskParamsError(`subtask type "${type}" takes no params, got: ${extra.join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const parsed = found.params.safeParse(supplied);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
const detail = parsed.error.issues
|
|
59
|
+
.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
|
|
60
|
+
.join("; ");
|
|
61
|
+
throw new SubtaskParamsError(`subtask type "${type}" has invalid params — ${detail}`);
|
|
62
|
+
}
|
|
63
|
+
return parsed.data;
|
|
64
|
+
},
|
|
65
|
+
paramProperties() {
|
|
66
|
+
const owners = new Map();
|
|
67
|
+
const declared = new Map();
|
|
68
|
+
for (const s of specs) {
|
|
69
|
+
if (!s.params)
|
|
70
|
+
continue;
|
|
71
|
+
for (const [key, field] of Object.entries(s.params.shape)) {
|
|
72
|
+
owners.set(key, [...(owners.get(key) ?? []), s.key]);
|
|
73
|
+
declared.set(key, field);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return Object.fromEntries([...declared].map(([key, field]) => [
|
|
77
|
+
key,
|
|
78
|
+
// Described *after* `.optional()`, so the text sits on the schema this
|
|
79
|
+
// returns rather than on the type it wraps — readable by both the JSON
|
|
80
|
+
// Schema conversion and anything inspecting these properties directly.
|
|
81
|
+
field
|
|
82
|
+
.optional()
|
|
83
|
+
.describe(`${owners.get(key)?.join("/")}: ${field.description ?? key}`)
|
|
84
|
+
]));
|
|
85
|
+
},
|
|
86
|
+
renderTypes() {
|
|
87
|
+
return specs
|
|
88
|
+
.map((s) => {
|
|
89
|
+
const help = s.paramsHelp ? ` — ${s.paramsHelp}` : "";
|
|
90
|
+
return `- \`${s.key}\`: ${s.description}${help}`;
|
|
91
|
+
})
|
|
92
|
+
.join("\n");
|
|
93
|
+
},
|
|
94
|
+
renderDelegationGuidance(names) {
|
|
95
|
+
const sections = [];
|
|
96
|
+
for (const s of specs) {
|
|
97
|
+
if (s.delegationGuidance)
|
|
98
|
+
sections.push(s.delegationGuidance(names));
|
|
99
|
+
}
|
|
100
|
+
return sections.join("\n\n");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { ResolvedRecipe, SubtaskParams } from "../contract/recipe.js";
|
|
2
|
+
import type { RoundFailureKind } from "../agent/inference.js";
|
|
3
|
+
export type SubtaskStatus = "pending" | "running" | "completed" | "failed" | "canceled";
|
|
4
|
+
/** A per-caller, SQLite-assigned, monotonically increasing Subtask identifier. */
|
|
5
|
+
export type SubtaskId = number;
|
|
6
|
+
/**
|
|
7
|
+
* An exact, verbatim snapshot of one selected Session history message, copied
|
|
8
|
+
* onto the Subtask at decomposition time. The decomposition model selects which
|
|
9
|
+
* messages to reference; it never rewrites their content. User-turn provenance
|
|
10
|
+
* (author/channel) is already inline in the message text's `<turn>` wrapper.
|
|
11
|
+
*/
|
|
12
|
+
export interface SubtaskReference {
|
|
13
|
+
role: "user" | "assistant";
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One part of a Subtask's result. Text-only today; file/data kinds are additive
|
|
18
|
+
* later. Internal to the agent — the terminal A2A Task collapses these to a
|
|
19
|
+
* single text reply for the gatekeeper/human.
|
|
20
|
+
*/
|
|
21
|
+
export interface SubtaskResultPart {
|
|
22
|
+
kind: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creation input for one Subtask, before it is persisted. `ordinal` is derived
|
|
27
|
+
* from the draft's position in the decomposition array, which is the only thing
|
|
28
|
+
* that distinguishes one draft of a decomposition from another — they carry no
|
|
29
|
+
* identity of their own until the data layer assigns a {@link SubtaskId}.
|
|
30
|
+
*/
|
|
31
|
+
export interface SubtaskDraft {
|
|
32
|
+
type: string;
|
|
33
|
+
prompt: string;
|
|
34
|
+
references: SubtaskReference[];
|
|
35
|
+
/** The type's required inputs, already validated for shape. */
|
|
36
|
+
params: SubtaskParams;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Session state the parent resolved *from* a Subtask's params at execution
|
|
40
|
+
* start — the part of an execution's context the model can never supply.
|
|
41
|
+
*
|
|
42
|
+
* Deliberately outside {@link RecipeExecutionRequest}: it is not part of an
|
|
43
|
+
* execution's identity and it can change under us, so fingerprinting it would
|
|
44
|
+
* make a retry look like a different request. It travels as a separate argument,
|
|
45
|
+
* the same discipline as the chunk number.
|
|
46
|
+
*/
|
|
47
|
+
export type SubtaskRuntime = Record<string, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Narrow a {@link SubtaskRuntime} to the shape a plugin resolved for itself.
|
|
50
|
+
*
|
|
51
|
+
* Runtime is an open bag because core cannot know what a domain needs — the
|
|
52
|
+
* predecessor repo declared ARC's four fields (`cardId`, `cookies`, `guid`,
|
|
53
|
+
* `frame`) right here in the delegation types, which meant core imported a
|
|
54
|
+
* domain's types and every unrelated agent carried them. A plugin now writes and
|
|
55
|
+
* reads its own slice, and the only thing core does with the bag is carry it.
|
|
56
|
+
*
|
|
57
|
+
* The unchecked cast is the point: the plugin that wrote the slice is the plugin
|
|
58
|
+
* reading it, so the assertion is local and its blast radius is one module.
|
|
59
|
+
*/
|
|
60
|
+
export declare function runtimeAs<T>(runtime: SubtaskRuntime): T;
|
|
61
|
+
/**
|
|
62
|
+
* A user-facing progress note a tool emits mid-execution (e.g. a game level-up).
|
|
63
|
+
* The resumable runner collects these and ends the current chunk so the parent
|
|
64
|
+
* DO can post them promptly; `key` is a stable dedupe id the gatekeeper keys on.
|
|
65
|
+
*/
|
|
66
|
+
export interface ProgressEvent {
|
|
67
|
+
key: string;
|
|
68
|
+
text: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* One durable chunk's outcome as the facet reports it to the parent DO. `done`
|
|
72
|
+
* false means the run yielded a chunk boundary and the Workflow must run another
|
|
73
|
+
* chunk; `done` true carries the terminal {@link RecipeExecutionResult}. Progress
|
|
74
|
+
* events accumulated during the chunk ride along either way.
|
|
75
|
+
*/
|
|
76
|
+
export type RecipeChunkResult = {
|
|
77
|
+
done: false;
|
|
78
|
+
progress: ProgressEvent[];
|
|
79
|
+
} | {
|
|
80
|
+
done: true;
|
|
81
|
+
result: RecipeExecutionResult;
|
|
82
|
+
progress: ProgressEvent[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The parent DO's projection of a chunk outcome for the Workflow (RPC-safe, no
|
|
86
|
+
* result parts — those are persisted on the row). `status` is `running` until the
|
|
87
|
+
* run is `done`, then the terminal Subtask status.
|
|
88
|
+
*/
|
|
89
|
+
export interface SubtaskChunkOutcome {
|
|
90
|
+
done: boolean;
|
|
91
|
+
status: SubtaskStatus;
|
|
92
|
+
progress: ProgressEvent[];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* RPC-safe input for one isolated `RecipeSubagent` execution, assembled by the
|
|
96
|
+
* parent at execution start: the already-resolved (and code-validated) Recipe,
|
|
97
|
+
* the Subtask's non-session prompt, and its verbatim reference snapshots. The
|
|
98
|
+
* child re-validates the Recipe defensively but never resolves one itself.
|
|
99
|
+
*/
|
|
100
|
+
export interface RecipeExecutionRequest {
|
|
101
|
+
taskId: string;
|
|
102
|
+
subtaskId: SubtaskId;
|
|
103
|
+
/**
|
|
104
|
+
* The Subtask's semantic type — what the work *is*, and what owns the params
|
|
105
|
+
* contract. Distinct from `recipe.key`, which names the execution
|
|
106
|
+
* configuration it runs under: several types may share one Recipe.
|
|
107
|
+
*/
|
|
108
|
+
type: string;
|
|
109
|
+
recipe: ResolvedRecipe;
|
|
110
|
+
prompt: string;
|
|
111
|
+
references: SubtaskReference[];
|
|
112
|
+
/**
|
|
113
|
+
* The Subtask's validated params. Part of the execution's identity — two plays
|
|
114
|
+
* of different games are different work — so this IS fingerprinted, unlike
|
|
115
|
+
* {@link SubtaskRuntime}, which is deliberately excluded: the leased scorecard
|
|
116
|
+
* can legitimately differ between two chunks of one run, and must not make a
|
|
117
|
+
* retry look like a different execution.
|
|
118
|
+
*/
|
|
119
|
+
params: SubtaskParams;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Terminal outcome of one `RecipeSubagent` execution (RPC-safe). `modelId` is a
|
|
123
|
+
* diagnostic only — which model produced the outcome (null when validation
|
|
124
|
+
* failed before any model call); it is never persisted on the Subtask row.
|
|
125
|
+
* Transient platform faults are not results: they throw so the enclosing
|
|
126
|
+
* Workflow step can retry.
|
|
127
|
+
*/
|
|
128
|
+
export type RecipeExecutionResult = {
|
|
129
|
+
status: "completed";
|
|
130
|
+
resultParts: SubtaskResultPart[];
|
|
131
|
+
modelId: string;
|
|
132
|
+
} | {
|
|
133
|
+
status: "failed";
|
|
134
|
+
error: string;
|
|
135
|
+
modelId: string | null;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* One Subtask as the delegating model emits it. The model selects references
|
|
139
|
+
* by **catalog index only** — it never emits reference text, and application code
|
|
140
|
+
* snapshots the catalog entry's exact role+text onto the Subtask (see
|
|
141
|
+
* `agent/subtasks/decomposition.ts`).
|
|
142
|
+
*/
|
|
143
|
+
export interface SubtaskProposal {
|
|
144
|
+
type: string;
|
|
145
|
+
prompt: string;
|
|
146
|
+
/**
|
|
147
|
+
* 1-based indices into the ephemeral, per-round reference catalog. Optional:
|
|
148
|
+
* omitted when the subtask needs no verbatim history, and always absent from a
|
|
149
|
+
* *reconstructed* historical call, whose references were resolved rounds ago.
|
|
150
|
+
*/
|
|
151
|
+
referenceIndexes?: number[];
|
|
152
|
+
/** The type's required inputs; omitted for a type that takes none. */
|
|
153
|
+
params?: SubtaskParams;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The model's complete `delegate` call: the acknowledgment the user sees while
|
|
157
|
+
* the work runs, plus one through eight Subtask proposals. Validated against the
|
|
158
|
+
* round's ephemeral catalog before anything is persisted; invalid output fails
|
|
159
|
+
* the attempt (and, with both models exhausted, the round) rather than being
|
|
160
|
+
* silently repaired.
|
|
161
|
+
*/
|
|
162
|
+
export interface DecompositionProposal {
|
|
163
|
+
reply: string;
|
|
164
|
+
subtasks: SubtaskProposal[];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Terminal outcome of one main-agent round (RPC-safe).
|
|
168
|
+
*
|
|
169
|
+
* `replied` is the terminal answer — the round chose to answer the user rather
|
|
170
|
+
* than delegate, and the Workflow delivers it. `delegated` means the round's
|
|
171
|
+
* Subtask rows are durable and the Workflow should execute them, after which
|
|
172
|
+
* another round begins. `failed` means the round produced no answer, with
|
|
173
|
+
* {@link RoundFailureKind} carrying why; no Subtask is ever synthesized to cover
|
|
174
|
+
* for it. `canceled` means the caller cancelled during the round: nothing was
|
|
175
|
+
* persisted and nothing was published. Transient platform faults are not
|
|
176
|
+
* results: they throw so the enclosing Workflow step can retry (mirrors
|
|
177
|
+
* {@link RecipeExecutionResult}).
|
|
178
|
+
*
|
|
179
|
+
* `turns` is what this round cost, which the Workflow meters against the Task's
|
|
180
|
+
* budget. This is the **only** type that carries it, and it carries it because the
|
|
181
|
+
* count has to cross an RPC boundary to reach a Workflow in another isolate;
|
|
182
|
+
* everything inside the DO shares one mutable
|
|
183
|
+
* {@link file://../agent/budget.ts TurnBudget} instead. The field is attached in a single place — see `runTaskTurn` — so no
|
|
184
|
+
* branch can drop it and no branch can invent it.
|
|
185
|
+
*
|
|
186
|
+
* The idempotent recovery paths — a round replayed from durable rows rather than
|
|
187
|
+
* re-inferred — therefore report **0** structurally: they return before any model
|
|
188
|
+
* runs, so the budget they hand back is untouched. That is exact on a clean
|
|
189
|
+
* replay, where the Workflow's own cached step return already carries the original
|
|
190
|
+
* number, and under-counts by one round when a step crashed mid-flight and re-ran.
|
|
191
|
+
* Accepted rather than fixed: a replying round writes no durable row at all, so
|
|
192
|
+
* nothing exists to hang a per-round count on, and the wall clock bounds the
|
|
193
|
+
* crash-loop case anyway.
|
|
194
|
+
*/
|
|
195
|
+
export type TurnTaskResult = {
|
|
196
|
+
status: "replied";
|
|
197
|
+
reply: string;
|
|
198
|
+
turns: number;
|
|
199
|
+
} | {
|
|
200
|
+
status: "delegated";
|
|
201
|
+
reply: string;
|
|
202
|
+
subtasks: Subtask[];
|
|
203
|
+
turns: number;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* `kind` distinguishes a round that spent both models and got nothing usable
|
|
207
|
+
* (`exhausted`) from one that stopped on a fault no attempt could clear —
|
|
208
|
+
* where the fallback was deliberately *not* tried, and the kind is what lets
|
|
209
|
+
* the host say why in words an operator can act on. Same terminal Task either
|
|
210
|
+
* way; only the words differ.
|
|
211
|
+
*/
|
|
212
|
+
| {
|
|
213
|
+
status: "failed";
|
|
214
|
+
kind: RoundFailureKind;
|
|
215
|
+
error: string;
|
|
216
|
+
turns: number;
|
|
217
|
+
} | {
|
|
218
|
+
status: "canceled";
|
|
219
|
+
turns: number;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Distributive `Omit` — applies per member, so the discriminated union survives.
|
|
223
|
+
* A plain `Omit<TurnTaskResult, "turns">` collapses all four into one loose shape
|
|
224
|
+
* whose `reply` and `subtasks` are independently optional.
|
|
225
|
+
*/
|
|
226
|
+
type WithoutTurns<T> = T extends unknown ? Omit<T, "turns"> : never;
|
|
227
|
+
/**
|
|
228
|
+
* What a round decided, before its cost is attached. The DO's round logic returns
|
|
229
|
+
* this and lets one wrapper bill the budget, rather than every branch remembering
|
|
230
|
+
* to report a number it did not compute.
|
|
231
|
+
*/
|
|
232
|
+
export type TurnVerdict = WithoutTurns<TurnTaskResult>;
|
|
233
|
+
/**
|
|
234
|
+
* One branch's outcome as a later round sees it — a plain, RPC-safe subset of the
|
|
235
|
+
* durable {@link Subtask} row, loaded across **every** round in stable ordinal
|
|
236
|
+
* order. Completed and failed branches are both included so the reply can use
|
|
237
|
+
* available successes and disclose relevant failures.
|
|
238
|
+
*
|
|
239
|
+
* Carries `round` and `prompt` — not for composing, but for reconstructing the
|
|
240
|
+
* per-round `delegate` call that produced these branches (see
|
|
241
|
+
* `agent/subtasks/delegate.ts`). `references` stays out: it is unbounded history
|
|
242
|
+
* text, and the call's shape does not need it.
|
|
243
|
+
*/
|
|
244
|
+
export interface CompositionBranch {
|
|
245
|
+
subtaskId: SubtaskId;
|
|
246
|
+
round: number;
|
|
247
|
+
ordinal: number;
|
|
248
|
+
type: string;
|
|
249
|
+
prompt: string;
|
|
250
|
+
params: SubtaskParams;
|
|
251
|
+
status: SubtaskStatus;
|
|
252
|
+
resultParts: SubtaskResultPart[] | null;
|
|
253
|
+
error: string | null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The `scan:<round>` projection (RPC-safe): either the caller cancelled, or the
|
|
257
|
+
* ids of the round's Subtasks that still owe an outcome, in ordinal order.
|
|
258
|
+
* Returning the verdict with the ids is what lets the Workflow drop its separate
|
|
259
|
+
* cancellation probe — one round trip, and no gap between asking and acting.
|
|
260
|
+
*
|
|
261
|
+
* Ids and nothing else, deliberately: a Workflow step return is capped at 1 MiB
|
|
262
|
+
* and a Subtask carries verbatim history snapshots bounded only by
|
|
263
|
+
* `MAX_INBOUND_TEXT_BYTES`, so a scan returning rows would overflow on a large
|
|
264
|
+
* task. The durable rows are the source of truth; the Workflow carries
|
|
265
|
+
* references to them and re-reads through the parent when it needs more.
|
|
266
|
+
*/
|
|
267
|
+
export type SubtaskScan = {
|
|
268
|
+
canceled: true;
|
|
269
|
+
} | {
|
|
270
|
+
canceled: false;
|
|
271
|
+
ids: SubtaskId[];
|
|
272
|
+
};
|
|
273
|
+
/** Durable state owned by the main agent for one delegated unit of work. */
|
|
274
|
+
export interface Subtask {
|
|
275
|
+
id: SubtaskId;
|
|
276
|
+
taskId: string;
|
|
277
|
+
/** The main-agent round that delegated this Subtask (0-based). */
|
|
278
|
+
round: number;
|
|
279
|
+
/** Position within the parent Task, increasing across every round. */
|
|
280
|
+
ordinal: number;
|
|
281
|
+
type: string;
|
|
282
|
+
recipeId: string | null;
|
|
283
|
+
recipeVersion: number | null;
|
|
284
|
+
prompt: string;
|
|
285
|
+
references: SubtaskReference[];
|
|
286
|
+
/** The type's required inputs, validated at delegation time. */
|
|
287
|
+
params: SubtaskParams;
|
|
288
|
+
status: SubtaskStatus;
|
|
289
|
+
resultParts: SubtaskResultPart[] | null;
|
|
290
|
+
error: string | null;
|
|
291
|
+
createdAt: number;
|
|
292
|
+
updatedAt: number;
|
|
293
|
+
completedAt: number | null;
|
|
294
|
+
}
|
|
295
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow a {@link SubtaskRuntime} to the shape a plugin resolved for itself.
|
|
3
|
+
*
|
|
4
|
+
* Runtime is an open bag because core cannot know what a domain needs — the
|
|
5
|
+
* predecessor repo declared ARC's four fields (`cardId`, `cookies`, `guid`,
|
|
6
|
+
* `frame`) right here in the delegation types, which meant core imported a
|
|
7
|
+
* domain's types and every unrelated agent carried them. A plugin now writes and
|
|
8
|
+
* reads its own slice, and the only thing core does with the bag is carry it.
|
|
9
|
+
*
|
|
10
|
+
* The unchecked cast is the point: the plugin that wrote the slice is the plugin
|
|
11
|
+
* reading it, so the assertion is local and its blast radius is one module.
|
|
12
|
+
*/
|
|
13
|
+
export function runtimeAs(runtime) {
|
|
14
|
+
return runtime;
|
|
15
|
+
}
|