@loopingai/core 0.7.0 → 0.8.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/README.md +37 -5
- package/dist/a2a/caller-token.d.ts +6 -0
- package/dist/a2a/index.d.ts +1 -0
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/self-origin.d.ts +91 -0
- package/dist/a2a/self-origin.js +114 -0
- package/dist/agent/errors.d.ts +16 -16
- package/dist/agent/errors.js +1 -1
- package/dist/agent/inference.d.ts +7 -5
- package/dist/agent/inference.js +0 -2
- package/dist/agent/model.d.ts +11 -5
- package/dist/agent/workers-ai/index.d.ts +13 -13
- package/dist/agent/workers-ai/index.js +13 -13
- package/dist/config.d.ts +3 -18
- package/dist/config.js +0 -4
- package/dist/host/agent.d.ts +41 -6
- package/dist/host/agent.js +49 -6
- package/dist/round/agent.js +18 -2
- package/dist/round/subagent.d.ts +7 -4
- package/dist/round/subagent.js +7 -4
- package/dist/subagent/index.d.ts +26 -5
- package/dist/subagent/index.js +35 -5
- package/dist/subtasks/delegate.d.ts +4 -3
- package/dist/subtasks/delegate.js +4 -3
- package/dist/testing/mock-model.js +1 -1
- package/package.json +1 -15
- package/scripts/generate-keys.mjs +15 -20
- package/dist/agent/anthropic/index.d.ts +0 -15
- package/dist/agent/anthropic/index.js +0 -19
- package/dist/agent/anthropic/language-model.d.ts +0 -59
- package/dist/agent/anthropic/language-model.js +0 -442
- package/dist/agent/anthropic/prompt.d.ts +0 -84
- package/dist/agent/anthropic/prompt.js +0 -541
- package/dist/agent/anthropic/runtime.d.ts +0 -79
- package/dist/agent/anthropic/runtime.js +0 -130
package/dist/host/agent.js
CHANGED
|
@@ -5,6 +5,7 @@ import { resolveConfig } from "../config.js";
|
|
|
5
5
|
import { AgentDB, stateOf } from "../db/index.js";
|
|
6
6
|
import { callerContext } from "../a2a/caller.js";
|
|
7
7
|
import { createPushChannel } from "../a2a/push.js";
|
|
8
|
+
import { SelfOrigin } from "../a2a/self-origin.js";
|
|
8
9
|
import { buildAgentSession } from "../agent/session.js";
|
|
9
10
|
import { workersAIModels } from "../agent/workers-ai/index.js";
|
|
10
11
|
/**
|
|
@@ -64,6 +65,17 @@ export class LoopingAgent extends Agent {
|
|
|
64
65
|
* not depend on it surviving.
|
|
65
66
|
*/
|
|
66
67
|
identityKey;
|
|
68
|
+
/**
|
|
69
|
+
* This deployment's own public origin, learned from the `jku` every turn
|
|
70
|
+
* carries and **pinned on the first one** this instance serves.
|
|
71
|
+
*
|
|
72
|
+
* Unlike {@link identityKey} this is shared by concurrent turns — the object
|
|
73
|
+
* is keyed by caller, not by origin — so it is pinned rather than
|
|
74
|
+
* last-write-wins: an immutable field cannot change under a credential thunk
|
|
75
|
+
* that reads it while a turn awaits a model call. See {@link SelfOrigin} for
|
|
76
|
+
* the full argument, and for why nothing is persisted.
|
|
77
|
+
*/
|
|
78
|
+
selfOriginMemo = new SelfOrigin();
|
|
67
79
|
/**
|
|
68
80
|
* Test-only model injection. A **field**, not a constructor argument or an RPC
|
|
69
81
|
* parameter, so it never appears on the generated DO stub: production callers
|
|
@@ -113,12 +125,11 @@ export class LoopingAgent extends Agent {
|
|
|
113
125
|
* a trap that only shows up as a performance bug. This is called once.
|
|
114
126
|
*
|
|
115
127
|
* `ModelRuntime` is the whole contract: return anything satisfying it and
|
|
116
|
-
* every loop in core keeps working unchanged. Core ships
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* anything on this path.
|
|
128
|
+
* every loop in core keeps working unchanged. Core ships one implementation,
|
|
129
|
+
* {@link file://../agent/workers-ai/index.ts `agent/workers-ai`} (the default
|
|
130
|
+
* below); a second provider is one more
|
|
131
|
+
* {@link file://../agent/model.ts ModelRuntimeFactory}, defined here or in the
|
|
132
|
+
* consumer, not a change to anything on this path.
|
|
122
133
|
*
|
|
123
134
|
* Takes the resolved {@link ModelConfig} rather than reading `this.config`, so
|
|
124
135
|
* that this signature matches
|
|
@@ -254,8 +265,40 @@ export class LoopingAgent extends Agent {
|
|
|
254
265
|
}
|
|
255
266
|
return (this.identityKey = key);
|
|
256
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Offer this deployment's own origin from a value that carries it. The first
|
|
270
|
+
* usable one is kept for the life of the instance.
|
|
271
|
+
*
|
|
272
|
+
* Called wherever a {@link TurnPushContext} arrives — here for every agent
|
|
273
|
+
* shape, and at the entry of `RoundAgentBase`'s two RPCs, where the origin is
|
|
274
|
+
* needed *before* this channel would be built. All three matter because any of
|
|
275
|
+
* them can be the call that wakes a fresh isolate. Cheap and unfailing: past
|
|
276
|
+
* the first turn it is one truthiness check, and an unusable value is ignored
|
|
277
|
+
* rather than thrown, because a turn must not fail over this.
|
|
278
|
+
*/
|
|
279
|
+
noteSelfOrigin(url) {
|
|
280
|
+
this.selfOriginMemo.note(url);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* This deployment's own public origin, if a turn has carried it to this
|
|
284
|
+
* instance yet. Constant once set, so it reads the same from any turn running
|
|
285
|
+
* on this object. See {@link SelfOrigin}.
|
|
286
|
+
*/
|
|
287
|
+
selfOrigin() {
|
|
288
|
+
return this.selfOriginMemo.peek();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The same, for a caller that cannot proceed without it — signing a caller
|
|
292
|
+
* token with {@link file://../a2a/caller-token.ts signCallerToken} above all,
|
|
293
|
+
* whose `iss` this is. Throws naming the timing rather than producing a token
|
|
294
|
+
* with a nonsense issuer.
|
|
295
|
+
*/
|
|
296
|
+
requireSelfOrigin() {
|
|
297
|
+
return this.selfOriginMemo.require();
|
|
298
|
+
}
|
|
257
299
|
/** The gateway callback channel for one turn. See {@link PushChannel}. */
|
|
258
300
|
push(context) {
|
|
301
|
+
this.noteSelfOrigin(context.jku);
|
|
259
302
|
return createPushChannel(this.env.A2A_SIGNING_KEY, context);
|
|
260
303
|
}
|
|
261
304
|
/**
|
package/dist/round/agent.js
CHANGED
|
@@ -83,6 +83,9 @@ export class RoundAgentBase extends LoopingAgent {
|
|
|
83
83
|
* number, and none can report the wrong one.
|
|
84
84
|
*/
|
|
85
85
|
async runTaskTurn(input) {
|
|
86
|
+
// Before anything can reach a model: a round that calls out mint-signed
|
|
87
|
+
// needs this deployment's own origin, and this is where it arrives.
|
|
88
|
+
this.noteSelfOrigin(input.push?.jku);
|
|
86
89
|
const budget = newTurnBudget(input.turnsRemaining);
|
|
87
90
|
const verdict = await this.decideRound(input, budget);
|
|
88
91
|
return { ...verdict, turns: budget.spent };
|
|
@@ -300,6 +303,10 @@ export class RoundAgentBase extends LoopingAgent {
|
|
|
300
303
|
* not outcomes.
|
|
301
304
|
*/
|
|
302
305
|
async executeSubtaskChunk(id, chunk, push) {
|
|
306
|
+
// Recorded here rather than left to `this.push(push)` below, which runs only
|
|
307
|
+
// after the chunk has already executed — and the child is handed this
|
|
308
|
+
// origin on the way in.
|
|
309
|
+
this.noteSelfOrigin(push?.jku);
|
|
303
310
|
const prepared = await this.prepareChunk(id);
|
|
304
311
|
if (prepared.kind === "terminal") {
|
|
305
312
|
return { done: true, status: prepared.subtask.status, progress: [] };
|
|
@@ -482,9 +489,18 @@ export class RoundAgentBase extends LoopingAgent {
|
|
|
482
489
|
* a second mismatch is a genuine lifecycle bug and must surface).
|
|
483
490
|
*/
|
|
484
491
|
async executeChunkInChild(name, request, chunk, runtime) {
|
|
492
|
+
// A facet has no request path of its own: it is reached only from here, so
|
|
493
|
+
// this is the only way it can learn what this deployment is called. Passed
|
|
494
|
+
// as its own argument, never folded into `request`, for the same reason
|
|
495
|
+
// `chunk` is — the request is fingerprinted, and this is not part of what
|
|
496
|
+
// the execution *is*, so it must not be able to make a retry look like a
|
|
497
|
+
// different one. Pinned on both sides, so it cannot change under a run;
|
|
498
|
+
// undefined only on an instance no turn has reached, where the facet's own
|
|
499
|
+
// `requireSelfOrigin` produces the readable error.
|
|
500
|
+
const selfOrigin = this.selfOrigin();
|
|
485
501
|
const child = await this.subAgent(this.subagentClass(), name);
|
|
486
502
|
try {
|
|
487
|
-
return await child.executeChunk(request, chunk, runtime);
|
|
503
|
+
return await child.executeChunk(request, chunk, runtime, selfOrigin);
|
|
488
504
|
}
|
|
489
505
|
catch (err) {
|
|
490
506
|
if (!String(err).includes(FINGERPRINT_MISMATCH))
|
|
@@ -492,7 +508,7 @@ export class RoundAgentBase extends LoopingAgent {
|
|
|
492
508
|
console.warn("[agent] stale subagent state, recreating", { name });
|
|
493
509
|
await this.deleteSubAgent(this.subagentClass(), name);
|
|
494
510
|
const fresh = await this.subAgent(this.subagentClass(), name);
|
|
495
|
-
return await fresh.executeChunk(request, chunk, runtime);
|
|
511
|
+
return await fresh.executeChunk(request, chunk, runtime, selfOrigin);
|
|
496
512
|
}
|
|
497
513
|
}
|
|
498
514
|
/** Let the owning plugin release whatever `resolveRuntime` acquired. */
|
package/dist/round/subagent.d.ts
CHANGED
|
@@ -59,14 +59,17 @@ export declare abstract class RecipeSubagentHost<TEnv extends Cloudflare.Env & A
|
|
|
59
59
|
* Which provider this facet's chunks run on. Mirrors
|
|
60
60
|
* {@link file://../host/agent.ts LoopingAgent.modelRuntime}, and **must be
|
|
61
61
|
* overridden to match it** — a facet that keeps the Workers AI default while
|
|
62
|
-
* its parent runs on
|
|
63
|
-
* different model than the round that delegated it.
|
|
62
|
+
* its parent runs on another provider would silently execute every subtask on
|
|
63
|
+
* a different model than the round that delegated it.
|
|
64
64
|
*
|
|
65
65
|
* The two seams take the same arguments precisely so that keeping them in step
|
|
66
66
|
* needs no discipline: write the provider once as a
|
|
67
67
|
* {@link file://../agent/model.ts ModelRuntimeFactory} and have both return
|
|
68
|
-
* it. Two hand-copied
|
|
69
|
-
*
|
|
68
|
+
* it. Two hand-copied runtime-construction bodies is what this shape exists to
|
|
69
|
+
* stop, because nothing type-checks their agreement.
|
|
70
|
+
*
|
|
71
|
+
* Note the cheapest way to satisfy this is to override *neither* seam, which
|
|
72
|
+
* is what an agent on core's default does.
|
|
70
73
|
*
|
|
71
74
|
* Takes the model config rather than reading `this.config`, because the facet
|
|
72
75
|
* resolves its config inside `buildRuntime` and this is called from there.
|
package/dist/round/subagent.js
CHANGED
|
@@ -54,14 +54,17 @@ export class RecipeSubagentHost extends RecipeSubagentBase {
|
|
|
54
54
|
* Which provider this facet's chunks run on. Mirrors
|
|
55
55
|
* {@link file://../host/agent.ts LoopingAgent.modelRuntime}, and **must be
|
|
56
56
|
* overridden to match it** — a facet that keeps the Workers AI default while
|
|
57
|
-
* its parent runs on
|
|
58
|
-
* different model than the round that delegated it.
|
|
57
|
+
* its parent runs on another provider would silently execute every subtask on
|
|
58
|
+
* a different model than the round that delegated it.
|
|
59
59
|
*
|
|
60
60
|
* The two seams take the same arguments precisely so that keeping them in step
|
|
61
61
|
* needs no discipline: write the provider once as a
|
|
62
62
|
* {@link file://../agent/model.ts ModelRuntimeFactory} and have both return
|
|
63
|
-
* it. Two hand-copied
|
|
64
|
-
*
|
|
63
|
+
* it. Two hand-copied runtime-construction bodies is what this shape exists to
|
|
64
|
+
* stop, because nothing type-checks their agreement.
|
|
65
|
+
*
|
|
66
|
+
* Note the cheapest way to satisfy this is to override *neither* seam, which
|
|
67
|
+
* is what an agent on core's default does.
|
|
65
68
|
*
|
|
66
69
|
* Takes the model config rather than reading `this.config`, because the facet
|
|
67
70
|
* resolves its config inside `buildRuntime` and this is called from there.
|
package/dist/subagent/index.d.ts
CHANGED
|
@@ -102,6 +102,14 @@ export declare abstract class RecipeSubagentBase<TEnv extends Cloudflare.Env = C
|
|
|
102
102
|
* to interrupt. See {@link abortRun}.
|
|
103
103
|
*/
|
|
104
104
|
private inflight?;
|
|
105
|
+
/**
|
|
106
|
+
* This deployment's own public origin, as the parent DO passes it on every
|
|
107
|
+
* chunk, pinned from the first. In memory for the same reason {@link inflight}
|
|
108
|
+
* is: a facet is reached only through {@link executeChunk}, so an instance that
|
|
109
|
+
* lost it is an instance that will be told again before it can run anything.
|
|
110
|
+
* See {@link SelfOrigin}.
|
|
111
|
+
*/
|
|
112
|
+
private readonly selfOriginMemo;
|
|
105
113
|
onStart(): Promise<void>;
|
|
106
114
|
/**
|
|
107
115
|
* Idempotent schema bootstrap. Also called lazily from the RPCs so
|
|
@@ -111,17 +119,30 @@ export declare abstract class RecipeSubagentBase<TEnv extends Cloudflare.Env = C
|
|
|
111
119
|
private ensureTables;
|
|
112
120
|
/** The recipe's durable workspace, backed by this facet's own SQLite storage. */
|
|
113
121
|
private workspace;
|
|
122
|
+
/**
|
|
123
|
+
* This deployment's own public origin, if the parent has passed it to this
|
|
124
|
+
* instance yet. See {@link SelfOrigin}.
|
|
125
|
+
*/
|
|
126
|
+
protected selfOrigin(): string | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* The same, for a caller that cannot proceed without it — a facet that signs
|
|
129
|
+
* its own caller tokens, above all. Mirrors `LoopingAgent.requireSelfOrigin`,
|
|
130
|
+
* because a facet must run on the same provider, and so the same credential
|
|
131
|
+
* path, as the parent that delegated to it.
|
|
132
|
+
*/
|
|
133
|
+
protected requireSelfOrigin(): string;
|
|
114
134
|
/**
|
|
115
135
|
* Execute one durable chunk of a Subtask under the parent's resolved Recipe.
|
|
116
136
|
*
|
|
117
137
|
* A terminal outcome (completed / failed) is cached and replayed on retry. A
|
|
118
138
|
* mid-run chunk persists its rolling state to `run_state` and returns a
|
|
119
|
-
* `done: false` yield for the Workflow to run another chunk. `chunk`
|
|
120
|
-
* separate
|
|
121
|
-
* identically and the cache/resume keys line up. Only
|
|
122
|
-
* throw (nothing cached), so a Workflow retry
|
|
139
|
+
* `done: false` yield for the Workflow to run another chunk. `chunk` and
|
|
140
|
+
* `selfOrigin` are separate arguments — never part of `request` — so every
|
|
141
|
+
* chunk fingerprints identically and the cache/resume keys line up. Only
|
|
142
|
+
* transient platform faults throw (nothing cached), so a Workflow retry
|
|
143
|
+
* resumes from the last checkpoint.
|
|
123
144
|
*/
|
|
124
|
-
executeChunk(request: RecipeExecutionRequest, _chunk: number, runtime?: SubtaskRuntime): Promise<RecipeChunkResult>;
|
|
145
|
+
executeChunk(request: RecipeExecutionRequest, _chunk: number, runtime?: SubtaskRuntime, selfOrigin?: string): Promise<RecipeChunkResult>;
|
|
125
146
|
/**
|
|
126
147
|
* Interrupt the chunk running here right now, so a cancellation lands on the
|
|
127
148
|
* current model call instead of at the next chunk boundary (up to `chunkSoftMs`
|
package/dist/subagent/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { CHUNK_SOFT_MS } from "../platform.js";
|
|
|
4
4
|
import { buildRecipeTools } from "../runtime/tool-families.js";
|
|
5
5
|
import { RecipeValidationError, validateRecipe } from "../contract/validation.js";
|
|
6
6
|
import { SubtaskParamsError } from "../subtasks/subtask-types.js";
|
|
7
|
+
import { SelfOrigin } from "../a2a/self-origin.js";
|
|
7
8
|
import { renderSubagentPrompt } from "./prompt.js";
|
|
8
9
|
import { makeWorkspaceHandle } from "./workspace.js";
|
|
9
10
|
import { fingerprintRequest } from "./fingerprint.js";
|
|
@@ -77,6 +78,14 @@ export class RecipeSubagentBase extends Agent {
|
|
|
77
78
|
* to interrupt. See {@link abortRun}.
|
|
78
79
|
*/
|
|
79
80
|
inflight;
|
|
81
|
+
/**
|
|
82
|
+
* This deployment's own public origin, as the parent DO passes it on every
|
|
83
|
+
* chunk, pinned from the first. In memory for the same reason {@link inflight}
|
|
84
|
+
* is: a facet is reached only through {@link executeChunk}, so an instance that
|
|
85
|
+
* lost it is an instance that will be told again before it can run anything.
|
|
86
|
+
* See {@link SelfOrigin}.
|
|
87
|
+
*/
|
|
88
|
+
selfOriginMemo = new SelfOrigin();
|
|
80
89
|
async onStart() {
|
|
81
90
|
this.ensureTables();
|
|
82
91
|
}
|
|
@@ -107,18 +116,39 @@ export class RecipeSubagentBase extends Agent {
|
|
|
107
116
|
workspace() {
|
|
108
117
|
return (this._workspace ??= this.subagentRuntime().workspaceBacking(this.ctx.storage.sql, () => this.name));
|
|
109
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* This deployment's own public origin, if the parent has passed it to this
|
|
121
|
+
* instance yet. See {@link SelfOrigin}.
|
|
122
|
+
*/
|
|
123
|
+
selfOrigin() {
|
|
124
|
+
return this.selfOriginMemo.peek();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The same, for a caller that cannot proceed without it — a facet that signs
|
|
128
|
+
* its own caller tokens, above all. Mirrors `LoopingAgent.requireSelfOrigin`,
|
|
129
|
+
* because a facet must run on the same provider, and so the same credential
|
|
130
|
+
* path, as the parent that delegated to it.
|
|
131
|
+
*/
|
|
132
|
+
requireSelfOrigin() {
|
|
133
|
+
return this.selfOriginMemo.require();
|
|
134
|
+
}
|
|
110
135
|
/**
|
|
111
136
|
* Execute one durable chunk of a Subtask under the parent's resolved Recipe.
|
|
112
137
|
*
|
|
113
138
|
* A terminal outcome (completed / failed) is cached and replayed on retry. A
|
|
114
139
|
* mid-run chunk persists its rolling state to `run_state` and returns a
|
|
115
|
-
* `done: false` yield for the Workflow to run another chunk. `chunk`
|
|
116
|
-
* separate
|
|
117
|
-
* identically and the cache/resume keys line up. Only
|
|
118
|
-
* throw (nothing cached), so a Workflow retry
|
|
140
|
+
* `done: false` yield for the Workflow to run another chunk. `chunk` and
|
|
141
|
+
* `selfOrigin` are separate arguments — never part of `request` — so every
|
|
142
|
+
* chunk fingerprints identically and the cache/resume keys line up. Only
|
|
143
|
+
* transient platform faults throw (nothing cached), so a Workflow retry
|
|
144
|
+
* resumes from the last checkpoint.
|
|
119
145
|
*/
|
|
120
|
-
async executeChunk(request, _chunk, runtime = {}) {
|
|
146
|
+
async executeChunk(request, _chunk, runtime = {}, selfOrigin) {
|
|
121
147
|
this.ensureTables();
|
|
148
|
+
// Before `subagentRuntime()`, which is where a host builds its model runtime
|
|
149
|
+
// — and a facet running on a provider it authenticates to mint-signed reads
|
|
150
|
+
// this origin from there.
|
|
151
|
+
this.selfOriginMemo.note(selfOrigin);
|
|
122
152
|
const rt = this.subagentRuntime();
|
|
123
153
|
const fingerprint = await fingerprintRequest(request);
|
|
124
154
|
// A terminal result already exists → replay it (idempotent retry).
|
|
@@ -54,9 +54,10 @@ export declare function makeDelegateTool(types: SubtaskTypeRegistry, maxSubtasks
|
|
|
54
54
|
* why it took a Claude-backed agent to surface it.
|
|
55
55
|
*
|
|
56
56
|
* Nothing persists this: both halves of the pair are rebuilt together on every
|
|
57
|
-
* request, so changing the shape needs no migration.
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* request, so changing the shape needs no migration. There is no longer a
|
|
58
|
+
* provider-side backstop in core — the adapter that carried one went with
|
|
59
|
+
* `./anthropic` in 0.8.0 — so a provider added here that validates tool-call ids
|
|
60
|
+
* needs to sanitize them on its own way out.
|
|
60
61
|
*/
|
|
61
62
|
export declare function delegateToolCallId(taskId: string, round: number): string;
|
|
62
63
|
/**
|
|
@@ -60,9 +60,10 @@ export function makeDelegateTool(types, maxSubtasks) {
|
|
|
60
60
|
* why it took a Claude-backed agent to surface it.
|
|
61
61
|
*
|
|
62
62
|
* Nothing persists this: both halves of the pair are rebuilt together on every
|
|
63
|
-
* request, so changing the shape needs no migration.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
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.
|
|
66
67
|
*/
|
|
67
68
|
export function delegateToolCallId(taskId, round) {
|
|
68
69
|
return `task_${taskId}_round_${round}_delegate`;
|
|
@@ -122,7 +122,7 @@ export function rateLimitedModel(failures, ...steps) {
|
|
|
122
122
|
if (calls <= failures) {
|
|
123
123
|
throw new APICallError({
|
|
124
124
|
message: "429 Wholesale Rate limited",
|
|
125
|
-
url: "
|
|
125
|
+
url: "mock:chat:test",
|
|
126
126
|
requestBodyValues: {},
|
|
127
127
|
statusCode: 429,
|
|
128
128
|
responseHeaders: { "retry-after": "0" }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loopingai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Shared, mandatory foundation for Looping agents on Cloudflare Workers: zero-trust A2A, durable task lifecycle, delegation and subagent runtime, test harness.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"a2a",
|
|
@@ -94,10 +94,6 @@
|
|
|
94
94
|
"import": "./dist/testing/vcr-global-setup.js"
|
|
95
95
|
},
|
|
96
96
|
"./eslint": "./eslint-rules/index.js",
|
|
97
|
-
"./anthropic": {
|
|
98
|
-
"types": "./dist/agent/anthropic/index.d.ts",
|
|
99
|
-
"import": "./dist/agent/anthropic/index.js"
|
|
100
|
-
},
|
|
101
97
|
"./package.json": "./package.json"
|
|
102
98
|
},
|
|
103
99
|
"scripts": {
|
|
@@ -123,8 +119,6 @@
|
|
|
123
119
|
"zod": "^4.4.3"
|
|
124
120
|
},
|
|
125
121
|
"peerDependencies": {
|
|
126
|
-
"@ai-sdk/provider": "^4.0.0",
|
|
127
|
-
"@anthropic-ai/sdk": "^0.116.0",
|
|
128
122
|
"@cloudflare/vitest-pool-workers": ">=0.18",
|
|
129
123
|
"@typescript-eslint/utils": ">=8",
|
|
130
124
|
"agents": "^0.20.0",
|
|
@@ -133,12 +127,6 @@
|
|
|
133
127
|
"workers-ai-provider": "^4.0.0"
|
|
134
128
|
},
|
|
135
129
|
"peerDependenciesMeta": {
|
|
136
|
-
"@ai-sdk/provider": {
|
|
137
|
-
"optional": true
|
|
138
|
-
},
|
|
139
|
-
"@anthropic-ai/sdk": {
|
|
140
|
-
"optional": true
|
|
141
|
-
},
|
|
142
130
|
"@cloudflare/vitest-pool-workers": {
|
|
143
131
|
"optional": true
|
|
144
132
|
},
|
|
@@ -150,8 +138,6 @@
|
|
|
150
138
|
}
|
|
151
139
|
},
|
|
152
140
|
"devDependencies": {
|
|
153
|
-
"@ai-sdk/provider": "^4.0.0",
|
|
154
|
-
"@anthropic-ai/sdk": "^0.116.0",
|
|
155
141
|
"@cloudflare/vitest-pool-workers": "^0.20.1",
|
|
156
142
|
"@types/node": "^26.1.1",
|
|
157
143
|
"agents": "^0.20.0",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* registration (Trust-On-First-Use). No secret is shared in either direction —
|
|
8
8
|
* see `src/a2a/verify.ts` for the other half of the contract.
|
|
9
9
|
*
|
|
10
|
-
* node scripts/generate-keys.mjs # print
|
|
10
|
+
* node scripts/generate-keys.mjs # print the private JWK
|
|
11
11
|
* node scripts/generate-keys.mjs --kid my-key # choose the key id
|
|
12
12
|
*
|
|
13
13
|
* The `kid` is required, not decorative: it goes in the JWS protected header and
|
|
@@ -24,30 +24,25 @@ const kid =
|
|
|
24
24
|
? args[kidFlag + 1]
|
|
25
25
|
: `a2a-${new Date().toISOString().slice(0, 10)}`;
|
|
26
26
|
|
|
27
|
-
// `extractable` is required to export the private half at all.
|
|
28
|
-
|
|
27
|
+
// `extractable` is required to export the private half at all. Only the
|
|
28
|
+
// private half is printed: the public one is derived from it at runtime and
|
|
29
|
+
// served at the JWKS route, so there is nothing to copy anywhere.
|
|
30
|
+
const { privateKey } = await generateKeyPair("EdDSA", {
|
|
29
31
|
crv: "Ed25519",
|
|
30
32
|
extractable: true
|
|
31
33
|
});
|
|
32
34
|
|
|
33
35
|
const priv = { ...(await exportJWK(privateKey)), kid };
|
|
34
|
-
const pub = { ...(await exportJWK(publicKey)), kid, use: "sig", alg: "EdDSA" };
|
|
35
36
|
|
|
36
|
-
console.log(
|
|
37
|
-
console.log(JSON.stringify(priv));
|
|
37
|
+
console.log(`\nGenerated Ed25519 keypair (kid: ${kid})\n`);
|
|
38
38
|
console.log(
|
|
39
|
-
"
|
|
39
|
+
"Add this line to .env — it is the private half, never commit it:\n"
|
|
40
|
+
);
|
|
41
|
+
console.log(`A2A_SIGNING_KEY=${JSON.stringify(priv)}\n`);
|
|
42
|
+
console.log(
|
|
43
|
+
"To deploy it, push the whole file or just this one secret:\n\n" +
|
|
44
|
+
" npx wrangler deploy --secrets-file .env\n" +
|
|
45
|
+
" npx wrangler secret put A2A_SIGNING_KEY\n\n" +
|
|
46
|
+
"The public half is not configured anywhere — the Worker derives it from\n" +
|
|
47
|
+
"the private key and serves it at the JWKS route.\n"
|
|
40
48
|
);
|
|
41
|
-
console.log(JSON.stringify({ keys: [pub] }, null, 2));
|
|
42
|
-
console.log(`
|
|
43
|
-
Next:
|
|
44
|
-
|
|
45
|
-
# local
|
|
46
|
-
echo 'A2A_SIGNING_KEY=<the private JSON above>' >> .dev.vars
|
|
47
|
-
|
|
48
|
-
# deployed
|
|
49
|
-
npx wrangler secret put A2A_SIGNING_KEY
|
|
50
|
-
|
|
51
|
-
The public half is not configured anywhere — the Worker derives it from the
|
|
52
|
-
private key and serves it at the JWKS route.
|
|
53
|
-
`);
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@loopingai/core/anthropic` — Claude as a second model provider.
|
|
3
|
-
*
|
|
4
|
-
* Its own subpath, and an **optional** peer dependency on `@anthropic-ai/sdk`,
|
|
5
|
-
* for the same reason `/round` is not re-exported from the root barrel: an agent
|
|
6
|
-
* that runs on Workers AI should not pay — in install size, in bundle bytes, or
|
|
7
|
-
* in a dependency it must keep current — for a provider it never calls.
|
|
8
|
-
*
|
|
9
|
-
* What lives here is a provider, not a capability. It ships no tools, no prompt
|
|
10
|
-
* copy and no policy; it satisfies {@link ModelRuntime} and stops.
|
|
11
|
-
*/
|
|
12
|
-
export { CredentialRejectedError, type CredentialRejectedBy } from "../errors.js";
|
|
13
|
-
export { createAnthropicLanguageModel, type AnthropicModelDeps } from "./language-model.js";
|
|
14
|
-
export { createAnthropicModelRuntime, type AnthropicRuntimeDeps } from "./runtime.js";
|
|
15
|
-
export { ANTHROPIC_PROVIDER, type CacheTtl } from "./prompt.js";
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@loopingai/core/anthropic` — Claude as a second model provider.
|
|
3
|
-
*
|
|
4
|
-
* Its own subpath, and an **optional** peer dependency on `@anthropic-ai/sdk`,
|
|
5
|
-
* for the same reason `/round` is not re-exported from the root barrel: an agent
|
|
6
|
-
* that runs on Workers AI should not pay — in install size, in bundle bytes, or
|
|
7
|
-
* in a dependency it must keep current — for a provider it never calls.
|
|
8
|
-
*
|
|
9
|
-
* What lives here is a provider, not a capability. It ships no tools, no prompt
|
|
10
|
-
* copy and no policy; it satisfies {@link ModelRuntime} and stops.
|
|
11
|
-
*/
|
|
12
|
-
// Re-exported, not owned: a rejected credential is a fact about the path to a
|
|
13
|
-
// model, not about Anthropic, so the error lives with the rest of the provider
|
|
14
|
-
// contract in {@link file://../errors.ts}. An agent that only imports this
|
|
15
|
-
// subpath still gets it from one place.
|
|
16
|
-
export { CredentialRejectedError } from "../errors.js";
|
|
17
|
-
export { createAnthropicLanguageModel } from "./language-model.js";
|
|
18
|
-
export { createAnthropicModelRuntime } from "./runtime.js";
|
|
19
|
-
export { ANTHROPIC_PROVIDER } from "./prompt.js";
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
import type { LanguageModelV4 } from "@ai-sdk/provider";
|
|
3
|
-
import { type CredentialRejectedBy } from "../errors.js";
|
|
4
|
-
import { type CacheTtl } from "./prompt.js";
|
|
5
|
-
/**
|
|
6
|
-
* A `LanguageModelV4` over `@anthropic-ai/sdk`, so core's loops can call Claude
|
|
7
|
-
* without knowing they are.
|
|
8
|
-
*
|
|
9
|
-
* Every loop in core is written against `generateText` from `ai` and a
|
|
10
|
-
* `LanguageModel` — {@link file://../../round/turn.ts turn.ts},
|
|
11
|
-
* {@link file://../../subagent/run.ts run.ts} and
|
|
12
|
-
* {@link file://../session.ts session.ts}. Satisfying that interface is what
|
|
13
|
-
* keeps the round loop, the control-tool repair ladder, subtask execution and
|
|
14
|
-
* the Workflow untouched by a second provider. The alternative — a bespoke
|
|
15
|
-
* Messages-API loop for one agent — would have given all of that up.
|
|
16
|
-
*
|
|
17
|
-
* `ai@7` accepts `LanguageModelV2 | V3 | V4`; this targets **v4**, the newest
|
|
18
|
-
* the installed `@ai-sdk/provider` defines.
|
|
19
|
-
*/
|
|
20
|
-
/** How the adapter reaches the API. Everything is injected so nothing reads env. */
|
|
21
|
-
export interface AnthropicModelDeps {
|
|
22
|
-
/**
|
|
23
|
-
* Constructed lazily by the runtime — see the note in
|
|
24
|
-
* {@link file://./runtime.ts}. Awaited, because building it has to resolve
|
|
25
|
-
* `env.AI.gateway(id).getUrl()`, which is async.
|
|
26
|
-
*/
|
|
27
|
-
client: () => Anthropic | Promise<Anthropic>;
|
|
28
|
-
/** Anthropic model id, e.g. `claude-opus-5`. */
|
|
29
|
-
modelId: string;
|
|
30
|
-
/** `max_tokens` when a caller supplies none. Anthropic requires the field. */
|
|
31
|
-
defaultMaxTokens: number;
|
|
32
|
-
/** Prompt-cache TTL, or `false` to place no breakpoints. Defaults to `"5m"`. */
|
|
33
|
-
cache?: CacheTtl | false;
|
|
34
|
-
/**
|
|
35
|
-
* Reasoning effort, when the caller does not set one per-call.
|
|
36
|
-
*
|
|
37
|
-
* Maps to `output_config.effort`. Coding and agentic work wants `"xhigh"`;
|
|
38
|
-
* `"high"` is the API default. Core never picks this — an agent does.
|
|
39
|
-
*/
|
|
40
|
-
effort?: "low" | "medium" | "high" | "xhigh" | "max";
|
|
41
|
-
/** Extra headers merged into every request (AI Gateway metadata lives here). */
|
|
42
|
-
headers?: Record<string, string>;
|
|
43
|
-
/**
|
|
44
|
-
* Recognise a deployment-specific authority in a `401`/`403` body.
|
|
45
|
-
*
|
|
46
|
-
* Consulted before the built-in shapes; return `undefined` to fall through to
|
|
47
|
-
* them. It exists because a deployment may put an authenticated intermediary
|
|
48
|
-
* between the gateway and Anthropic, and only that deployment knows what its
|
|
49
|
-
* refusal looks like — core recognising one particular proxy's error body
|
|
50
|
-
* would be exactly the deployment policy this package does not ship.
|
|
51
|
-
*
|
|
52
|
-
* The remedy is what makes it worth distinguishing at all: a proxy that mints
|
|
53
|
-
* its caller credential per request has no secret to rotate, so reporting its
|
|
54
|
-
* `401` as `credential` sends an operator to replace a working token. See
|
|
55
|
-
* {@link file://../errors.ts CredentialRejectedBy}.
|
|
56
|
-
*/
|
|
57
|
-
classifyAuthFailure?: (body: unknown) => CredentialRejectedBy | undefined;
|
|
58
|
-
}
|
|
59
|
-
export declare function createAnthropicLanguageModel(deps: AnthropicModelDeps): LanguageModelV4;
|