@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,369 @@
|
|
|
1
|
+
import { AGENT_CARD_PATH, SendMessageRequest } from "@a2a-js/sdk";
|
|
2
|
+
import { DefaultRequestHandler, JsonRpcTransportHandler, validateVersion } from "@a2a-js/sdk/server";
|
|
3
|
+
import { RequestMalformedError, toJsonRpcError } from "@a2a-js/sdk/errors";
|
|
4
|
+
import { JWKS_PATH, endpointUrl } from "@dynamicagents/g2a-protocol";
|
|
5
|
+
import { A2A_RPC_PATH, buildBaseCard, parsePrivateJwk, publicCardJwks, signCard, signCardInPlace } from "../a2a/card.js";
|
|
6
|
+
import { buildCallContext, extensionHeaders } from "../a2a/context.js";
|
|
7
|
+
import { GatekeeperAuthError, bearerToken, verifyGatekeeperToken } from "../a2a/verify.js";
|
|
8
|
+
import { A2AExecutor } from "../a2a/executor.js";
|
|
9
|
+
import { DurableTaskStore } from "../a2a/task-store.js";
|
|
10
|
+
import { parseGatekeeperOrigins } from "../env.js";
|
|
11
|
+
/**
|
|
12
|
+
* The A2A Worker: the zero-trust, no-shared-secrets edge every Dynamic Agents agent
|
|
13
|
+
* puts in front of its Durable Object.
|
|
14
|
+
*
|
|
15
|
+
* Trust flows entirely on domains and asymmetric (Ed25519) signatures over
|
|
16
|
+
* public JWKS: a gatekeeper verifies and pins this agent's identity from its signed
|
|
17
|
+
* card at registration ("G knows R"), and this Worker verifies the gatekeeper's
|
|
18
|
+
* identity JWT on every JSON-RPC call ("R knows G"). No secret is ever shared in
|
|
19
|
+
* either direction.
|
|
20
|
+
*
|
|
21
|
+
* **Several agents share one Worker, addressed by `tenant`.** They share one
|
|
22
|
+
* origin, one endpoint, one signing key and one card at the well-known path;
|
|
23
|
+
* what differs is the tenant id on every request. That is the A2A mechanism for
|
|
24
|
+
* exactly this — `AgentInterface.tenant` is "an opaque string used for routing
|
|
25
|
+
* requests to a specific agent or tenant when multiple agents are served behind
|
|
26
|
+
* a single A2A endpoint", and §8.3.2 requires a client to send the value the
|
|
27
|
+
* selected interface declared.
|
|
28
|
+
*
|
|
29
|
+
* A tenant is **required on every request**. There is no default and no
|
|
30
|
+
* implicit routing: an absent tenant is an error, because guessing one would
|
|
31
|
+
* mean picking an agent the caller never named.
|
|
32
|
+
*
|
|
33
|
+
* This replaces mounting a handler per path prefix. That could not work: the
|
|
34
|
+
* card is a **well-known URI**, which RFC 8615 defines per-authority, so only
|
|
35
|
+
* one card per origin is discoverable at the registered path — a gatekeeper
|
|
36
|
+
* resolving `…/.well-known/agent-card.json` against the origin found whichever
|
|
37
|
+
* agent owned the bare path and pinned *its* key for all of them.
|
|
38
|
+
*
|
|
39
|
+
* Three routes:
|
|
40
|
+
*
|
|
41
|
+
* 1. `GET jwksPath` — the card-signing public JWKS, resolving every card's
|
|
42
|
+
* `jku`.
|
|
43
|
+
* 2. `GET …/.well-known/agent-card.json` — the signed **stub** card for the
|
|
44
|
+
* origin. Matched by suffix, since that path is fixed by the spec.
|
|
45
|
+
* 3. `POST rpcPath` — gatekeeper-authenticated JSON-RPC, routed to the named
|
|
46
|
+
* tenant. `GetExtendedAgentCard` returns that tenant's own signed card;
|
|
47
|
+
* everything else runs a turn against its Durable Object.
|
|
48
|
+
*
|
|
49
|
+
* Two independent checks keep one tenant's traffic out of another's:
|
|
50
|
+
* `audience` proves the token was minted for this deployment, and
|
|
51
|
+
* {@link file://../a2a/verify.ts TENANT_CLAIM} proves it was minted for *this
|
|
52
|
+
* agent on it*. The second is what makes `tenant` more than an unauthenticated
|
|
53
|
+
* routing hint in the request body.
|
|
54
|
+
*/
|
|
55
|
+
export { defineAgent } from "./define-agent.js";
|
|
56
|
+
/**
|
|
57
|
+
* Default path serving the card-signing public JWKS (the card's `jku`).
|
|
58
|
+
*
|
|
59
|
+
* From `@dynamicagents/g2a-protocol`, because the gatekeeper serves its own JWKS at
|
|
60
|
+
* the same path and fetches ours from whatever `jku` says — see
|
|
61
|
+
* {@link file://../a2a/verify.ts} for why the two sides share a package rather
|
|
62
|
+
* than a comment. Re-exported so this stays importable from
|
|
63
|
+
* `@dynamicagents/core/worker`, where it has always lived.
|
|
64
|
+
*/
|
|
65
|
+
export { JWKS_PATH } from "@dynamicagents/g2a-protocol";
|
|
66
|
+
/** The JSON-RPC method carrying a turn (v1.0 renamed v0.3's `message/send`). */
|
|
67
|
+
const SEND_MESSAGE_METHOD = "SendMessage";
|
|
68
|
+
function unauthorized(reason) {
|
|
69
|
+
return new Response(`unauthorized: ${reason}`, {
|
|
70
|
+
status: 401,
|
|
71
|
+
headers: { "www-authenticate": 'Bearer error="invalid_token"' }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A JSON-RPC error envelope for a failure raised before the SDK handler ran,
|
|
76
|
+
* echoing the request's `id` so a conformant client can correlate it. Errors are
|
|
77
|
+
* transported at HTTP 200 per JSON-RPC 2.0, matching what the SDK handler
|
|
78
|
+
* returns for the failures it maps itself.
|
|
79
|
+
*
|
|
80
|
+
* Takes an already-mapped `error` because the mapper has to match the error's
|
|
81
|
+
* origin: `@a2a-js/sdk/errors` and `@a2a-js/sdk/server` are separately bundled
|
|
82
|
+
* entry points that each carry their own copy of the error hierarchy, so a
|
|
83
|
+
* mapper only recognizes errors its own bundle constructed — the other one fails
|
|
84
|
+
* its `instanceof` check and flattens every semantic error to a generic internal
|
|
85
|
+
* error. Each call site below picks accordingly.
|
|
86
|
+
*/
|
|
87
|
+
function jsonRpcErrorResponse(body, error) {
|
|
88
|
+
const id = body?.id ?? null;
|
|
89
|
+
return Response.json({ jsonrpc: "2.0", id, error });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Why a `SendMessage` fails the async-only contract, or `undefined` when it
|
|
93
|
+
* holds. (Other methods carry no config and are always fine.)
|
|
94
|
+
*
|
|
95
|
+
* Checked here, before the SDK handler, rather than in the executor: an executor
|
|
96
|
+
* throw is turned into a `failed` task, which a client reads as an accepted turn
|
|
97
|
+
* that never calls back. A rejected send has to surface as a JSON-RPC error.
|
|
98
|
+
*/
|
|
99
|
+
function pushConfigError(rpcBody) {
|
|
100
|
+
if (rpcBody.method !== SEND_MESSAGE_METHOD)
|
|
101
|
+
return undefined;
|
|
102
|
+
// Decode through the generated codec rather than reading the raw body: the
|
|
103
|
+
// wire form is protobuf-JSON, so this is what normalizes field naming and
|
|
104
|
+
// oneof shapes into the typed request the SDK handler will also see.
|
|
105
|
+
let params;
|
|
106
|
+
try {
|
|
107
|
+
params = SendMessageRequest.fromJSON(rpcBody.params);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return `params are not a valid SendMessageRequest: ${err.message}`;
|
|
111
|
+
}
|
|
112
|
+
const pushConfig = params.configuration?.taskPushNotificationConfig;
|
|
113
|
+
if (!pushConfig?.url) {
|
|
114
|
+
return ("configuration.taskPushNotificationConfig.url is required: this agent " +
|
|
115
|
+
"replies asynchronously via push notification (A2A §13.2)");
|
|
116
|
+
}
|
|
117
|
+
if (!pushConfig.token) {
|
|
118
|
+
return ("configuration.taskPushNotificationConfig.token is required: the gatekeeper " +
|
|
119
|
+
"uses it to correlate the callback to the pending task (A2A §13.2)");
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
new URL(pushConfig.url);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return `configuration.taskPushNotificationConfig.url is not a valid URL: ${pushConfig.url}`;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
export function createA2AWorker(options) {
|
|
130
|
+
// Normalized once, here, because each of these is used for two things that
|
|
131
|
+
// must agree: routing compares it against `url.pathname`, and `endpointUrl`
|
|
132
|
+
// composes it into the card's interface URL and the expected audience. A
|
|
133
|
+
// configured path written without its leading slash used to satisfy neither —
|
|
134
|
+
// `url.pathname` always has one, so the route simply never matched, and the
|
|
135
|
+
// audience came out as `https://hostpath`. Normalizing at the split keeps the
|
|
136
|
+
// two derivations from disagreeing whichever way it is written.
|
|
137
|
+
const withSlash = (path) => path.startsWith("/") ? path : `/${path}`;
|
|
138
|
+
const jwksPath = withSlash(options.jwksPath ?? JWKS_PATH);
|
|
139
|
+
const rpcPath = withSlash(options.rpcPath ?? A2A_RPC_PATH);
|
|
140
|
+
const requirePushConfig = options.requirePushConfig ?? true;
|
|
141
|
+
const declared = options.tenants ?? {};
|
|
142
|
+
const defined = options.agents ?? [];
|
|
143
|
+
// A tenant declared both ways is ambiguous, and silently letting one win is how
|
|
144
|
+
// a deployment serves an agent nobody meant to mount. Caught at startup.
|
|
145
|
+
const duplicate = defined.find((a) => Object.hasOwn(declared, a.tenant));
|
|
146
|
+
if (duplicate) {
|
|
147
|
+
throw new Error(`createA2AWorker got tenant '${duplicate.tenant}' from both \`agents\` and ` +
|
|
148
|
+
"`tenants` — declare each agent once");
|
|
149
|
+
}
|
|
150
|
+
// …and the same ambiguity within `agents` alone, which the check above cannot
|
|
151
|
+
// see. `definedByTenant` below is a `Map`, so a repeated id silently keeps the
|
|
152
|
+
// *last* entry: two `defineAgent` calls sharing a tenant would mount one and
|
|
153
|
+
// discard the other with no signal anywhere. That is the identical failure the
|
|
154
|
+
// cross-check exists to prevent, reached by copy-pasting a declaration rather
|
|
155
|
+
// than by mixing the two forms — which is the likelier mistake of the two.
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
for (const agent of defined) {
|
|
158
|
+
if (seen.has(agent.tenant)) {
|
|
159
|
+
throw new Error(`createA2AWorker got tenant '${agent.tenant}' twice in \`agents\` — ` +
|
|
160
|
+
"declare each agent once");
|
|
161
|
+
}
|
|
162
|
+
seen.add(agent.tenant);
|
|
163
|
+
}
|
|
164
|
+
// Nothing can be served without at least one agent, and a deployment that
|
|
165
|
+
// configured none is a startup mistake rather than a per-request one.
|
|
166
|
+
const tenantIds = [...Object.keys(declared), ...defined.map((a) => a.tenant)];
|
|
167
|
+
if (tenantIds.length === 0) {
|
|
168
|
+
throw new Error("createA2AWorker requires at least one agent — pass `agents` or `tenants`");
|
|
169
|
+
}
|
|
170
|
+
// …and one registered under `""` is the same mistake wearing a disguise. A
|
|
171
|
+
// request must name a tenant, and the empty string is how "named none" is
|
|
172
|
+
// spelled, so that agent is registered and permanently unreachable — every
|
|
173
|
+
// call to it is refused as a missing tenant before the lookup ever runs. The
|
|
174
|
+
// check above would otherwise report a deployment with no routable agent as
|
|
175
|
+
// satisfying "at least one tenant".
|
|
176
|
+
if (tenantIds.some((id) => !id)) {
|
|
177
|
+
throw new Error("createA2AWorker requires every tenant id to be non-empty: a request " +
|
|
178
|
+
"names its tenant, so an agent registered under '' can never be reached");
|
|
179
|
+
}
|
|
180
|
+
const definedByTenant = new Map(defined.map((a) => [a.tenant, a]));
|
|
181
|
+
// The tenant id is caller-controlled, so the lookup is by *own* property.
|
|
182
|
+
// Plain indexing reaches `Object.prototype`, and every inherited name —
|
|
183
|
+
// `toString`, `constructor`, `__proto__` — comes back truthy, so a tenant
|
|
184
|
+
// called one of those slips past an `if (!agent)` guard and only fails later
|
|
185
|
+
// when a function is read off it. That is a 500 where the whole point of the
|
|
186
|
+
// guard is a 400 naming the unknown tenant. (A `Map` has no such inheritance,
|
|
187
|
+
// so the `agents` half needs no equivalent guard.)
|
|
188
|
+
//
|
|
189
|
+
// Takes `env` because a `defineAgent` entry resolves its bindings from it —
|
|
190
|
+
// there is no `env` at module scope on Workers, so the closure is built per
|
|
191
|
+
// request rather than at construction.
|
|
192
|
+
const tenantAgent = (env, id) => {
|
|
193
|
+
if (Object.hasOwn(declared, id))
|
|
194
|
+
return declared[id];
|
|
195
|
+
const agent = definedByTenant.get(id);
|
|
196
|
+
if (!agent)
|
|
197
|
+
return undefined;
|
|
198
|
+
return {
|
|
199
|
+
manifest: agent.manifest,
|
|
200
|
+
resolveAgent: (identity) => agent.resolveAgent(env, identity),
|
|
201
|
+
startTurn: (turn) => agent.startTurn(env, turn)
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
// Only reachable through the first overload, which has already established
|
|
205
|
+
// that `TEnv` carries the two documented names.
|
|
206
|
+
const readSecrets = options.secrets ??
|
|
207
|
+
((env) => ({
|
|
208
|
+
signingKey: env.A2A_SIGNING_KEY,
|
|
209
|
+
gatekeeperOrigins: env.GATEKEEPER_ORIGINS
|
|
210
|
+
}));
|
|
211
|
+
return async function fetch(request, env) {
|
|
212
|
+
const url = new URL(request.url);
|
|
213
|
+
const origin = url.origin;
|
|
214
|
+
const secrets = readSecrets(env);
|
|
215
|
+
// This agent's own endpoint, which is also exactly what its card advertises
|
|
216
|
+
// as its interface — so the value the gatekeeper was registered with and the
|
|
217
|
+
// value checked here are the same string by construction.
|
|
218
|
+
const audience = typeof options.audience === "function"
|
|
219
|
+
? options.audience(url)
|
|
220
|
+
: (options.audience ?? endpointUrl(origin, rpcPath));
|
|
221
|
+
const privateJwk = parsePrivateJwk(secrets.signingKey);
|
|
222
|
+
const signing = { privateJwk, jku: endpointUrl(origin, jwksPath) };
|
|
223
|
+
const cardFor = (manifest, tenant) => buildBaseCard(manifest, {
|
|
224
|
+
origin,
|
|
225
|
+
rpcPath: options.rpcPath,
|
|
226
|
+
advertiseSecuritySchemes: options.advertiseSecuritySchemes,
|
|
227
|
+
tenant
|
|
228
|
+
});
|
|
229
|
+
// (1) Card-signing public JWKS — resolves every card's `jku` for the
|
|
230
|
+
// gatekeeper. One key per origin, so one JWKS for every tenant.
|
|
231
|
+
if (request.method === "GET" && url.pathname === jwksPath) {
|
|
232
|
+
return Response.json(publicCardJwks(privateJwk), {
|
|
233
|
+
headers: { "cache-control": "public, max-age=3600" }
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// (2) The stub card, at the well-known path. `signCard` returns the
|
|
237
|
+
// protobuf-JSON encoding — the exact document the signature is computed
|
|
238
|
+
// over — so it is serialized here without re-encoding.
|
|
239
|
+
if (request.method === "GET" && url.pathname.endsWith(AGENT_CARD_PATH)) {
|
|
240
|
+
return Response.json(await signCard(cardFor(options.manifest), signing));
|
|
241
|
+
}
|
|
242
|
+
// (3) A2A JSON-RPC — gatekeeper-authenticated, dispatched into the caller's DO.
|
|
243
|
+
//
|
|
244
|
+
// Matched on `rpcPath`, not on the method alone. Accepting every POST made
|
|
245
|
+
// the path this agent advertises purely decorative: a call to any URL on the
|
|
246
|
+
// origin was served as JSON-RPC, so a mounted agent's isolation rested
|
|
247
|
+
// entirely on an outer router matching first, and a typo'd endpoint quietly
|
|
248
|
+
// worked instead of 404ing.
|
|
249
|
+
if (request.method === "POST" && url.pathname === rpcPath) {
|
|
250
|
+
const token = bearerToken(request);
|
|
251
|
+
if (!token)
|
|
252
|
+
return unauthorized("missing gatekeeper bearer token");
|
|
253
|
+
let identity;
|
|
254
|
+
let authorizedTenant;
|
|
255
|
+
try {
|
|
256
|
+
({ identity, tenant: authorizedTenant } = await verifyGatekeeperToken(token, {
|
|
257
|
+
allowedOrigins: parseGatekeeperOrigins(secrets.gatekeeperOrigins),
|
|
258
|
+
audience,
|
|
259
|
+
identityClaim: options.identityClaim,
|
|
260
|
+
tenantClaim: options.tenantClaim
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
const message = err instanceof GatekeeperAuthError
|
|
265
|
+
? err.message
|
|
266
|
+
: "verification failed";
|
|
267
|
+
return unauthorized(message);
|
|
268
|
+
}
|
|
269
|
+
// The DO instance is keyed by the verified `identity.key`; without it the
|
|
270
|
+
// executor cannot route the call — refuse rather than fall back to a
|
|
271
|
+
// shared instance. Guaranteed non-null past this point.
|
|
272
|
+
if (!identity.key) {
|
|
273
|
+
return new Response("bad request: gatekeeper identity missing key", {
|
|
274
|
+
status: 400
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const body = await request.json();
|
|
278
|
+
// `typeof null === "object"`, so the null check is load-bearing: a body of
|
|
279
|
+
// literal `null` is valid JSON and would otherwise make `rpcBody` null,
|
|
280
|
+
// throwing on the property reads below instead of reaching the SDK
|
|
281
|
+
// handler, which maps it to the same -32602 that `[]` or a string gets.
|
|
282
|
+
const rpcBody = (typeof body === "object" && body !== null ? body : {});
|
|
283
|
+
// Which agent this call is for. Read straight off the body rather than
|
|
284
|
+
// through a codec: every request message carries `tenant` in the same
|
|
285
|
+
// place, so decoding as one specific request type would mean knowing the
|
|
286
|
+
// method first, and the tenant is needed to pick the handler that would
|
|
287
|
+
// decide that.
|
|
288
|
+
const requestedTenant = rpcBody.params?.tenant ?? "";
|
|
289
|
+
if (typeof requestedTenant !== "string" || !requestedTenant) {
|
|
290
|
+
return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError("params.tenant is required: this endpoint serves several agents " +
|
|
291
|
+
"and none is the default. Use the tenant declared on the " +
|
|
292
|
+
"interface of the agent's card (A2A §8.3.2), or call " +
|
|
293
|
+
"GetExtendedAgentCard with it to fetch that card.")));
|
|
294
|
+
}
|
|
295
|
+
// The token names the tenant the gatekeeper authorized; the body names the
|
|
296
|
+
// one the call addressed. They must agree.
|
|
297
|
+
//
|
|
298
|
+
// This is the check that makes `tenant` more than a routing hint. Every
|
|
299
|
+
// tenant here shares one endpoint and so one `aud`, which means the
|
|
300
|
+
// audience cannot tell them apart — without this, a token legitimately
|
|
301
|
+
// minted for one agent could be replayed against any of its siblings by
|
|
302
|
+
// editing a field in the request body. An absent claim is a rejection
|
|
303
|
+
// rather than a wildcard for the same reason.
|
|
304
|
+
if (authorizedTenant !== requestedTenant) {
|
|
305
|
+
return unauthorized(`gatekeeper token authorizes tenant '${authorizedTenant || "<none>"}', ` +
|
|
306
|
+
`but the request addressed '${requestedTenant}'`);
|
|
307
|
+
}
|
|
308
|
+
const agent = tenantAgent(env, requestedTenant);
|
|
309
|
+
if (!agent) {
|
|
310
|
+
return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError(`unknown tenant '${requestedTenant}'`)));
|
|
311
|
+
}
|
|
312
|
+
const card = cardFor(agent.manifest, requestedTenant);
|
|
313
|
+
const context = buildCallContext(request, identity, requestedTenant);
|
|
314
|
+
// v1.0 negotiates the protocol version per request via the `A2A-Version`
|
|
315
|
+
// header, and the SDK leaves enforcement to the transport binding (its
|
|
316
|
+
// Express handlers do it; this Worker is the equivalent seam). An absent
|
|
317
|
+
// header is treated as `0.3` by the SDK, which a v1.0-only card does not
|
|
318
|
+
// advertise — so a legacy caller is rejected here rather than silently
|
|
319
|
+
// mis-served.
|
|
320
|
+
try {
|
|
321
|
+
validateVersion(context.requestedVersion, card, "JSONRPC");
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
// Thrown by the server bundle — map it with the server bundle's mapper.
|
|
325
|
+
return jsonRpcErrorResponse(body, JsonRpcTransportHandler.mapToJSONRPCError(err));
|
|
326
|
+
}
|
|
327
|
+
if (requirePushConfig) {
|
|
328
|
+
const contractError = pushConfigError(rpcBody);
|
|
329
|
+
if (contractError) {
|
|
330
|
+
// Constructed from the errors bundle — map it with that bundle's.
|
|
331
|
+
return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError(contractError)));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const handler = new DefaultRequestHandler(card, new DurableTaskStore(identity, agent.resolveAgent), new A2AExecutor({
|
|
335
|
+
identity,
|
|
336
|
+
jku: `${origin}${jwksPath}`,
|
|
337
|
+
resolveAgent: agent.resolveAgent,
|
|
338
|
+
startTurn: agent.startTurn
|
|
339
|
+
}), undefined, undefined, undefined,
|
|
340
|
+
// `GetExtendedAgentCard` — how a caller gets a *tenant's* card, since
|
|
341
|
+
// only the stub is discoverable at the well-known path.
|
|
342
|
+
//
|
|
343
|
+
// Returns the in-memory card and lets the SDK encode it: its JSON-RPC
|
|
344
|
+
// transport runs `AgentCard.toJSON()` over whatever comes back, so
|
|
345
|
+
// handing it `signCard`'s already-encoded wire card would encode twice
|
|
346
|
+
// and silently break the signature. See {@link signCardInPlace}.
|
|
347
|
+
//
|
|
348
|
+
// The tenant is read off the context rather than the request params
|
|
349
|
+
// because that is all the SDK passes; it is the value this Worker
|
|
350
|
+
// already authorized and routed on.
|
|
351
|
+
async (ctx) => {
|
|
352
|
+
const requested = ctx.tenant ?? "";
|
|
353
|
+
const target = tenantAgent(env, requested);
|
|
354
|
+
if (!target) {
|
|
355
|
+
throw new RequestMalformedError(`unknown tenant '${requested}'`);
|
|
356
|
+
}
|
|
357
|
+
return signCardInPlace(cardFor(target.manifest, requested), signing);
|
|
358
|
+
});
|
|
359
|
+
const rpc = new JsonRpcTransportHandler(handler);
|
|
360
|
+
const result = await rpc.handle(body, context);
|
|
361
|
+
// Streaming is not advertised; reject async generators outright.
|
|
362
|
+
if (Symbol.asyncIterator in result) {
|
|
363
|
+
return new Response("streaming not supported", { status: 501 });
|
|
364
|
+
}
|
|
365
|
+
return Response.json(result, { headers: extensionHeaders(context) });
|
|
366
|
+
}
|
|
367
|
+
return new Response("not found", { status: 404 });
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { noDeprecatedObjectProperties } from "./no-deprecated-object-properties.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `@dynamicagents/core/eslint` — the lint rules this stack needs that no upstream
|
|
5
|
+
* plugin provides.
|
|
6
|
+
*
|
|
7
|
+
* Consume as a flat-config plugin:
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* import da from "@dynamicagents/core/eslint";
|
|
11
|
+
*
|
|
12
|
+
* export default [
|
|
13
|
+
* {
|
|
14
|
+
* files: ["src/**\/*.ts"],
|
|
15
|
+
* plugins: { da },
|
|
16
|
+
* languageOptions: {
|
|
17
|
+
* parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname }
|
|
18
|
+
* },
|
|
19
|
+
* rules: { "da/no-deprecated-object-properties": "error" }
|
|
20
|
+
* }
|
|
21
|
+
* ];
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* `no-deprecated-object-properties` is type-aware, so the consuming config must
|
|
25
|
+
* enable `projectService` (or `project`) for it to see anything.
|
|
26
|
+
*/
|
|
27
|
+
export const rules = {
|
|
28
|
+
"no-deprecated-object-properties": noDeprecatedObjectProperties
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default { rules };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Flags `@deprecated` properties used as keys in object literals.
|
|
5
|
+
*
|
|
6
|
+
* `@typescript-eslint/no-deprecated` cannot see these. For an object literal it
|
|
7
|
+
* calls `getTypeAtLocation(objectExpression)`, which yields the literal's own
|
|
8
|
+
* *inferred* type — an anonymous type whose properties carry no JSDoc — rather
|
|
9
|
+
* than the contextual type that actually declares the deprecation. (Its JSX
|
|
10
|
+
* branch uses `getContextualType` and does work.) So member access like
|
|
11
|
+
* `opts.system` is reported, but `generateText({ system })` is silently allowed,
|
|
12
|
+
* even when the object is written to an explicitly annotated variable.
|
|
13
|
+
*
|
|
14
|
+
* That blind spot covers essentially every options-bag API — the AI SDK's
|
|
15
|
+
* `generateText`/`streamText` among them — so this rule closes it by resolving
|
|
16
|
+
* the contextual type itself and checking each key against it.
|
|
17
|
+
*
|
|
18
|
+
* Upstream issue: typescript-eslint#10883.
|
|
19
|
+
*/
|
|
20
|
+
export const noDeprecatedObjectProperties = ESLintUtils.RuleCreator.withoutDocs(
|
|
21
|
+
{
|
|
22
|
+
meta: {
|
|
23
|
+
type: "problem",
|
|
24
|
+
docs: {
|
|
25
|
+
description:
|
|
26
|
+
"Disallow `@deprecated` properties as keys in object literals"
|
|
27
|
+
},
|
|
28
|
+
messages: {
|
|
29
|
+
deprecated: "`{{name}}` is deprecated.{{reason}}"
|
|
30
|
+
},
|
|
31
|
+
schema: []
|
|
32
|
+
},
|
|
33
|
+
defaultOptions: [],
|
|
34
|
+
create(context) {
|
|
35
|
+
const services = ESLintUtils.getParserServices(context);
|
|
36
|
+
const checker = services.program.getTypeChecker();
|
|
37
|
+
|
|
38
|
+
/** Property name for a non-computed key, or undefined if not statically known. */
|
|
39
|
+
const staticName = (property) => {
|
|
40
|
+
if (property.computed) return undefined;
|
|
41
|
+
const { key } = property;
|
|
42
|
+
if (key.type === "Identifier") return key.name;
|
|
43
|
+
if (key.type === "Literal" && typeof key.value === "string")
|
|
44
|
+
return key.value;
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ObjectExpression(node) {
|
|
50
|
+
const tsNode = services.esTreeNodeToTSNodeMap.get(node);
|
|
51
|
+
// Undefined whenever the literal is not contextually typed (e.g. an
|
|
52
|
+
// unannotated `const`), in which case there is no declared property to
|
|
53
|
+
// consult and nothing to report.
|
|
54
|
+
const contextualType = checker.getContextualType(tsNode);
|
|
55
|
+
if (contextualType === undefined) return;
|
|
56
|
+
|
|
57
|
+
for (const property of node.properties) {
|
|
58
|
+
if (property.type !== "Property") continue; // skip SpreadElement
|
|
59
|
+
const name = staticName(property);
|
|
60
|
+
if (name === undefined) continue;
|
|
61
|
+
|
|
62
|
+
const symbol = contextualType.getProperty(name);
|
|
63
|
+
if (symbol === undefined) continue;
|
|
64
|
+
|
|
65
|
+
const tag = symbol
|
|
66
|
+
.getJsDocTags(checker)
|
|
67
|
+
.find((t) => t.name === "deprecated");
|
|
68
|
+
if (tag === undefined) continue;
|
|
69
|
+
|
|
70
|
+
const text = (tag.text ?? []).map((part) => part.text).join("");
|
|
71
|
+
context.report({
|
|
72
|
+
node: property.key,
|
|
73
|
+
messageId: "deprecated",
|
|
74
|
+
data: { name, reason: text ? ` ${text}` : "" }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dynamicagents/core",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Shared, mandatory foundation for Dynamic Agents on Cloudflare Workers: zero-trust A2A, durable task lifecycle, delegation and subagent runtime, test harness.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"a2a",
|
|
7
|
+
"agent2agent",
|
|
8
|
+
"ai-agents",
|
|
9
|
+
"cloudflare-workers",
|
|
10
|
+
"durable-objects",
|
|
11
|
+
"agents-sdk",
|
|
12
|
+
"zero-trust",
|
|
13
|
+
"jwks",
|
|
14
|
+
"eddsa",
|
|
15
|
+
"workflows"
|
|
16
|
+
],
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"author": "Dynamic Agents",
|
|
19
|
+
"homepage": "https://github.com/dynamicagents/core#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/dynamicagents/core.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/dynamicagents/core/issues"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"bin": {
|
|
30
|
+
"da-keys": "scripts/generate-keys.mjs"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"eslint-rules",
|
|
35
|
+
"scripts/generate-keys.mjs",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"exports": {
|
|
40
|
+
".": {
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"import": "./dist/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./a2a": {
|
|
45
|
+
"types": "./dist/a2a/index.d.ts",
|
|
46
|
+
"import": "./dist/a2a/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./worker": {
|
|
49
|
+
"types": "./dist/worker/index.d.ts",
|
|
50
|
+
"import": "./dist/worker/index.js"
|
|
51
|
+
},
|
|
52
|
+
"./agent": {
|
|
53
|
+
"types": "./dist/agent/index.d.ts",
|
|
54
|
+
"import": "./dist/agent/index.js"
|
|
55
|
+
},
|
|
56
|
+
"./host": {
|
|
57
|
+
"types": "./dist/host/index.d.ts",
|
|
58
|
+
"import": "./dist/host/index.js"
|
|
59
|
+
},
|
|
60
|
+
"./alarm": {
|
|
61
|
+
"types": "./dist/alarm/index.d.ts",
|
|
62
|
+
"import": "./dist/alarm/index.js"
|
|
63
|
+
},
|
|
64
|
+
"./job": {
|
|
65
|
+
"types": "./dist/job/index.d.ts",
|
|
66
|
+
"import": "./dist/job/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./round": {
|
|
69
|
+
"types": "./dist/round/index.d.ts",
|
|
70
|
+
"import": "./dist/round/index.js"
|
|
71
|
+
},
|
|
72
|
+
"./subtasks": {
|
|
73
|
+
"types": "./dist/subtasks/index.d.ts",
|
|
74
|
+
"import": "./dist/subtasks/index.js"
|
|
75
|
+
},
|
|
76
|
+
"./subagent": {
|
|
77
|
+
"types": "./dist/subagent/index.d.ts",
|
|
78
|
+
"import": "./dist/subagent/index.js"
|
|
79
|
+
},
|
|
80
|
+
"./db": {
|
|
81
|
+
"types": "./dist/db/index.d.ts",
|
|
82
|
+
"import": "./dist/db/index.js"
|
|
83
|
+
},
|
|
84
|
+
"./testing": {
|
|
85
|
+
"types": "./dist/testing/index.d.ts",
|
|
86
|
+
"import": "./dist/testing/index.js"
|
|
87
|
+
},
|
|
88
|
+
"./testing/fixtures": {
|
|
89
|
+
"types": "./dist/testing/fixtures.d.ts",
|
|
90
|
+
"import": "./dist/testing/fixtures.js"
|
|
91
|
+
},
|
|
92
|
+
"./testing/node": {
|
|
93
|
+
"types": "./dist/testing/node.d.ts",
|
|
94
|
+
"import": "./dist/testing/node.js"
|
|
95
|
+
},
|
|
96
|
+
"./testing/vcr-global-setup": {
|
|
97
|
+
"types": "./dist/testing/vcr-global-setup.d.ts",
|
|
98
|
+
"import": "./dist/testing/vcr-global-setup.js"
|
|
99
|
+
},
|
|
100
|
+
"./eslint": "./eslint-rules/index.js",
|
|
101
|
+
"./package.json": "./package.json"
|
|
102
|
+
},
|
|
103
|
+
"scripts": {
|
|
104
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
105
|
+
"db:generate": "drizzle-kit generate && node scripts/build-migrations.mjs",
|
|
106
|
+
"keys": "node scripts/generate-keys.mjs",
|
|
107
|
+
"types": "wrangler types --include-env=false",
|
|
108
|
+
"types:check": "wrangler types --include-env=false --check && node scripts/verify-runtime-types.mjs",
|
|
109
|
+
"format": "prettier --write .",
|
|
110
|
+
"lint": "eslint .",
|
|
111
|
+
"check": "npm run types:check && prettier --check . && eslint . && tsc && tsc -p test/tsconfig.json && npm run build",
|
|
112
|
+
"test": "vitest run",
|
|
113
|
+
"test:watch": "vitest",
|
|
114
|
+
"verify:exports": "node scripts/verify-exports.mjs",
|
|
115
|
+
"prepack": "npm run build && npm run verify:exports",
|
|
116
|
+
"prepublishOnly": "npm run check && npm test && npm run verify:exports",
|
|
117
|
+
"prepare": "husky"
|
|
118
|
+
},
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"@a2a-js/sdk": "^1.0.1",
|
|
121
|
+
"@dynamicagents/g2a-protocol": "^0.3.0",
|
|
122
|
+
"drizzle-orm": "^0.45.2",
|
|
123
|
+
"jose": "^6.2.10",
|
|
124
|
+
"zod": "^4.4.3"
|
|
125
|
+
},
|
|
126
|
+
"peerDependencies": {
|
|
127
|
+
"@cloudflare/vitest-pool-workers": ">=0.18",
|
|
128
|
+
"@typescript-eslint/utils": ">=8",
|
|
129
|
+
"agents": "^0.21.0",
|
|
130
|
+
"ai": "^7.0.77",
|
|
131
|
+
"vitest": ">=4",
|
|
132
|
+
"workers-ai-provider": "^4.0.0"
|
|
133
|
+
},
|
|
134
|
+
"peerDependenciesMeta": {
|
|
135
|
+
"@cloudflare/vitest-pool-workers": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@typescript-eslint/utils": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
141
|
+
"vitest": {
|
|
142
|
+
"optional": true
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"devDependencies": {
|
|
146
|
+
"@cloudflare/vitest-pool-workers": "^0.22.0",
|
|
147
|
+
"@types/node": "^26.2.0",
|
|
148
|
+
"agents": "^0.21.0",
|
|
149
|
+
"ai": "^7.0.77",
|
|
150
|
+
"drizzle-kit": "^0.31.10",
|
|
151
|
+
"eslint": "^10.9.0",
|
|
152
|
+
"husky": "^9.1.7",
|
|
153
|
+
"prettier": "^3.9.6",
|
|
154
|
+
"typescript": "^6.0.3",
|
|
155
|
+
"typescript-eslint": "^8.67.0",
|
|
156
|
+
"vitest": "^4.1.11",
|
|
157
|
+
"workers-ai-provider": "^4.0.0",
|
|
158
|
+
"wrangler": "^4.125.0"
|
|
159
|
+
},
|
|
160
|
+
"engines": {
|
|
161
|
+
"node": ">=24"
|
|
162
|
+
},
|
|
163
|
+
"publishConfig": {
|
|
164
|
+
"access": "public"
|
|
165
|
+
},
|
|
166
|
+
"overrides": {
|
|
167
|
+
"@esbuild-kit/core-utils": {
|
|
168
|
+
"esbuild": "^0.25.0"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"allowScripts": {
|
|
172
|
+
"esbuild": false,
|
|
173
|
+
"fsevents": false,
|
|
174
|
+
"sharp": false,
|
|
175
|
+
"workerd": false,
|
|
176
|
+
"core-js-pure": false
|
|
177
|
+
}
|
|
178
|
+
}
|