@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.
@@ -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 two implementations,
117
- * one directory each — {@link file://../agent/workers-ai/index.ts
118
- * `agent/workers-ai`} (the default below) and `@loopingai/core/anthropic`
119
- * and a third provider is a third directory exporting one
120
- * {@link file://../agent/model.ts ModelRuntimeFactory}, not a change to
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
  /**
@@ -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. */
@@ -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 Claude would silently execute every subtask on a
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 `createAnthropicModelRuntime({...})` bodies is what this
69
- * shape exists to stop, because nothing type-checks their agreement.
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.
@@ -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 Claude would silently execute every subtask on a
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 `createAnthropicModelRuntime({...})` bodies is what this
64
- * shape exists to stop, because nothing type-checks their agreement.
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.
@@ -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` is a
120
- * separate argument — never part of `request` — so every chunk fingerprints
121
- * identically and the cache/resume keys line up. Only transient platform faults
122
- * throw (nothing cached), so a Workflow retry resumes from the last checkpoint.
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`
@@ -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` is a
116
- * separate argument — never part of `request` — so every chunk fingerprints
117
- * identically and the cache/resume keys line up. Only transient platform faults
118
- * throw (nothing cached), so a Workflow retry resumes from the last checkpoint.
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. See
58
- * {@link file://../agent/anthropic/prompt.ts providerSafeToolCallId} for the
59
- * backstop that catches the next one of these.
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. See
64
- * {@link file://../agent/anthropic/prompt.ts providerSafeToolCallId} for the
65
- * backstop that catches the next one of these.
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: "anthropic:messages:test",
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.7.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 both halves
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
- const { privateKey, publicKey } = await generateKeyPair("EdDSA", {
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("A2A_SIGNING_KEY (private set as a secret, never commit):\n");
37
- console.log(JSON.stringify(priv));
37
+ console.log(`\nGenerated Ed25519 keypair (kid: ${kid})\n`);
38
38
  console.log(
39
- "\nPublic JWKS (served at the card's `jku`, derived at runtime):\n"
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;