@loopingai/core 0.5.1 → 0.7.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.
Files changed (99) hide show
  1. package/README.md +6 -4
  2. package/dist/a2a/caller-token.d.ts +38 -0
  3. package/dist/a2a/caller-token.js +61 -0
  4. package/dist/a2a/deliver.d.ts +49 -0
  5. package/dist/a2a/deliver.js +49 -0
  6. package/dist/a2a/index.d.ts +2 -0
  7. package/dist/a2a/index.js +2 -0
  8. package/dist/a2a/notify.d.ts +4 -3
  9. package/dist/a2a/notify.js +4 -3
  10. package/dist/agent/anthropic/index.d.ts +15 -0
  11. package/dist/agent/anthropic/index.js +19 -0
  12. package/dist/agent/anthropic/language-model.d.ts +59 -0
  13. package/dist/agent/anthropic/language-model.js +442 -0
  14. package/dist/agent/anthropic/prompt.d.ts +84 -0
  15. package/dist/agent/anthropic/prompt.js +541 -0
  16. package/dist/agent/anthropic/runtime.d.ts +79 -0
  17. package/dist/agent/anthropic/runtime.js +130 -0
  18. package/dist/agent/control.js +10 -9
  19. package/dist/agent/errors.d.ts +85 -0
  20. package/dist/agent/errors.js +64 -0
  21. package/dist/agent/final-reply.d.ts +14 -13
  22. package/dist/agent/final-reply.js +28 -11
  23. package/dist/agent/history.d.ts +3 -3
  24. package/dist/agent/history.js +2 -2
  25. package/dist/agent/index.d.ts +4 -2
  26. package/dist/agent/index.js +4 -2
  27. package/dist/agent/inference.d.ts +58 -1
  28. package/dist/agent/inference.js +44 -0
  29. package/dist/agent/model.d.ts +42 -25
  30. package/dist/agent/model.js +1 -48
  31. package/dist/agent/session.d.ts +6 -7
  32. package/dist/agent/session.js +3 -3
  33. package/dist/agent/workers-ai/index.d.ts +23 -0
  34. package/dist/agent/workers-ai/index.js +23 -0
  35. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  36. package/dist/agent/workers-ai/runtime.js +63 -0
  37. package/dist/alarm/index.d.ts +77 -0
  38. package/dist/alarm/index.js +116 -0
  39. package/dist/config.d.ts +49 -15
  40. package/dist/config.js +30 -1
  41. package/dist/contract/plugin.d.ts +63 -3
  42. package/dist/contract/plugin.js +76 -0
  43. package/dist/contract/recipe.d.ts +16 -17
  44. package/dist/db/db.d.ts +0 -1
  45. package/dist/db/migrations/index.js +8 -1
  46. package/dist/db/models/subtasks.d.ts +24 -25
  47. package/dist/db/models/subtasks.js +33 -76
  48. package/dist/db/schema.d.ts +2 -21
  49. package/dist/db/schema.js +2 -4
  50. package/dist/host/agent.d.ts +58 -4
  51. package/dist/host/agent.js +63 -9
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.js +2 -2
  54. package/dist/platform.d.ts +103 -11
  55. package/dist/platform.js +107 -13
  56. package/dist/round/agent.d.ts +36 -31
  57. package/dist/round/agent.js +61 -89
  58. package/dist/round/index.d.ts +3 -2
  59. package/dist/round/index.js +2 -2
  60. package/dist/round/policy.d.ts +2 -2
  61. package/dist/round/subagent.d.ts +19 -1
  62. package/dist/round/subagent.js +22 -5
  63. package/dist/round/turn.d.ts +32 -13
  64. package/dist/round/turn.js +83 -16
  65. package/dist/round/workflow.d.ts +23 -7
  66. package/dist/round/workflow.js +122 -110
  67. package/dist/runtime/index.d.ts +4 -2
  68. package/dist/runtime/index.js +6 -0
  69. package/dist/subagent/fingerprint.d.ts +2 -2
  70. package/dist/subagent/fingerprint.js +8 -17
  71. package/dist/subagent/index.d.ts +6 -4
  72. package/dist/subagent/index.js +8 -6
  73. package/dist/subagent/prompt.d.ts +4 -5
  74. package/dist/subagent/prompt.js +0 -8
  75. package/dist/subagent/run.d.ts +8 -1
  76. package/dist/subagent/run.js +59 -9
  77. package/dist/subtasks/catalog.d.ts +1 -1
  78. package/dist/subtasks/catalog.js +1 -1
  79. package/dist/subtasks/decomposition.d.ts +16 -20
  80. package/dist/subtasks/decomposition.js +27 -75
  81. package/dist/subtasks/delegate.d.ts +20 -1
  82. package/dist/subtasks/delegate.js +21 -16
  83. package/dist/subtasks/index.d.ts +1 -2
  84. package/dist/subtasks/index.js +1 -2
  85. package/dist/subtasks/subtask-types.d.ts +0 -8
  86. package/dist/subtasks/subtask-types.js +0 -7
  87. package/dist/subtasks/types.d.ts +45 -70
  88. package/dist/testing/do.d.ts +4 -4
  89. package/dist/testing/index.d.ts +1 -1
  90. package/dist/testing/index.js +5 -1
  91. package/dist/testing/mock-model.d.ts +35 -0
  92. package/dist/testing/mock-model.js +75 -0
  93. package/dist/testing/vcr-global-setup.d.ts +1 -3
  94. package/dist/testing/vcr-global-setup.js +1 -3
  95. package/dist/worker/index.d.ts +5 -12
  96. package/dist/worker/index.js +5 -12
  97. package/package.json +23 -1
  98. package/dist/subtasks/scheduler.d.ts +0 -48
  99. package/dist/subtasks/scheduler.js +0 -47
package/README.md CHANGED
@@ -188,8 +188,8 @@ probed for), and hand-rolling it is how two agents in one repo drift apart.
188
188
 
189
189
  ### 4. Delegate, if your agent delegates
190
190
 
191
- `@loopingai/core/round` adds the other half: a durable Subtask DAG, wave
192
- scheduling, isolated subagent execution, and the round loop over them.
191
+ `@loopingai/core/round` adds the other half: durable Subtasks, concurrent
192
+ execution, isolated subagents, and the round loop over them.
193
193
 
194
194
  ```ts
195
195
  import { RoundAgentBase, type RoundPolicy } from "@loopingai/core/round";
@@ -225,10 +225,11 @@ not drag in the A2A adapter, and the test harness cannot reach a production bund
225
225
  | `@loopingai/core` | `createAgentRuntime`, the plugin contract, config shapes, platform facts |
226
226
  | `@loopingai/core/a2a` | card signing, JWKS, gateway-JWT verify, push notify, task store, executor |
227
227
  | `@loopingai/core/worker` | `createA2AWorker()` — the whole zero-trust edge |
228
- | `@loopingai/core/agent` | session, history, model runtime, inference, budget, control tools |
228
+ | `@loopingai/core/agent` | session, history, models + Workers AI, inference, budget, control tools |
229
229
  | `@loopingai/core/host` | `LoopingAgent` — the Durable Object body — and `PluginHost` |
230
230
  | `@loopingai/core/round` | the delegating round loop: `RoundAgentBase`, `runHandleTask`, `runTurn` |
231
- | `@loopingai/core/subtasks` | delegation types, decomposition, the `delegate` tool, wave scheduler |
231
+ | `@loopingai/core/anthropic` | Claude as a second provider _optional peer on `@anthropic-ai/sdk`_ |
232
+ | `@loopingai/core/subtasks` | delegation types, decomposition, the `delegate` tool |
232
233
  | `@loopingai/core/subagent` | `RecipeSubagentBase`, resumable runs, fingerprinting, workspace |
233
234
  | `@loopingai/core/db` | `AgentDB`, `notify_tasks` + `subtasks` schema, migrations, `PluginStore` |
234
235
  | `@loopingai/core/testing` | VCR, `FakeSession`, `mockModel`, DO helpers, JWK fixtures — _workerd realm_ |
@@ -465,6 +466,7 @@ await withDb("accepts a turn once", async (db) => {
465
466
  - **Bindings:** `AI`, one Durable Object, one Workflow
466
467
  - **Secrets:** `A2A_SIGNING_KEY`, `GATEWAY_ORIGINS`
467
468
  - **Peers, never bundled:** `agents`, `ai`, `workers-ai-provider`
469
+ - **Optional peer:** `@anthropic-ai/sdk`, needed only by `@loopingai/core/anthropic`
468
470
 
469
471
  That last point is not stylistic: two copies of `agents` in one Worker breaks the
470
472
  `Session` / `SessionMessage` types and every `instanceof`. For local development
@@ -0,0 +1,38 @@
1
+ export interface CallerTokenOptions {
2
+ /** This agent's raw private JWK JSON — the same `A2A_SIGNING_KEY` its card is signed with. */
3
+ signingKey: string;
4
+ /**
5
+ * This agent's own origin. It becomes `iss`, and `jku` is derived from it, and
6
+ * the two must agree: a verifier that accepts a `jku` on a different origin
7
+ * than `iss` lets one allowlisted origin impersonate another.
8
+ */
9
+ issuer: string;
10
+ /**
11
+ * Who the token is for. Normalized to a bare origin, because a verifier
12
+ * typically derives what it expects from `new URL(request.url).origin` and
13
+ * `jose` compares `aud` byte-for-byte — a trailing slash or a stray path is a
14
+ * 401 on every request with nothing to catch it. Throws on a value that is not
15
+ * an absolute URL, which is the right moment for that to fail.
16
+ */
17
+ audience: string;
18
+ /** The identity this agent asserts. */
19
+ identity: Record<string, unknown>;
20
+ /** Which tenant of this deployment the token speaks for. */
21
+ tenant: string;
22
+ /** Lifetime in seconds. Defaults to 120. */
23
+ ttlSeconds?: number;
24
+ }
25
+ /**
26
+ * Sign a short-lived token identifying **this agent as a caller** to another
27
+ * service that trusts its card key.
28
+ *
29
+ * The production sibling of `makeGatewayToken`, which core previously shipped
30
+ * only from `/testing` — so an agent that had to call out mint-signed had to
31
+ * hand-write this shape, and every deployment that did so wrote its own subtly
32
+ * different version of the `iss`/`jku` agreement above.
33
+ *
34
+ * Distinct from {@link signCallbackJwt}, which carries **no** claims: that one
35
+ * proves "the agent you called is calling you back about this task", where this
36
+ * proves "this is who I am and which tenant I speak for".
37
+ */
38
+ export declare function signCallerToken(options: CallerTokenOptions): Promise<string>;
@@ -0,0 +1,61 @@
1
+ import { importJWK, SignJWT } from "jose";
2
+ import { A2A_JWS_ALG, IDENTITY_CLAIM, TENANT_CLAIM, jwksUrl } from "@loopingai/a2a-protocol";
3
+ import { parsePrivateJwk } from "./card.js";
4
+ /** Default lifetime: long enough for clock skew, short enough to be worthless from a log. */
5
+ const DEFAULT_TTL_SECONDS = 120;
6
+ /**
7
+ * `importJWK` does real work and this is on the per-request path.
8
+ *
9
+ * Keyed by the raw secret so a rotated key invalidates the entry rather than
10
+ * being ignored for the life of the isolate.
11
+ */
12
+ let cached;
13
+ async function signingKeyFor(raw) {
14
+ if (cached?.raw === raw)
15
+ return cached;
16
+ const jwk = parsePrivateJwk(raw);
17
+ // Not cast to `CryptoKey`: `importJWK` returns a union, and asserting the
18
+ // branch would be a lie the day this key is anything but Ed25519.
19
+ const key = await importJWK(jwk, A2A_JWS_ALG);
20
+ cached = { raw, key, kid: jwk.kid };
21
+ return cached;
22
+ }
23
+ /**
24
+ * Sign a short-lived token identifying **this agent as a caller** to another
25
+ * service that trusts its card key.
26
+ *
27
+ * The production sibling of `makeGatewayToken`, which core previously shipped
28
+ * only from `/testing` — so an agent that had to call out mint-signed had to
29
+ * hand-write this shape, and every deployment that did so wrote its own subtly
30
+ * different version of the `iss`/`jku` agreement above.
31
+ *
32
+ * Distinct from {@link signCallbackJwt}, which carries **no** claims: that one
33
+ * proves "the agent you called is calling you back about this task", where this
34
+ * proves "this is who I am and which tenant I speak for".
35
+ */
36
+ export async function signCallerToken(options) {
37
+ const { key, kid } = await signingKeyFor(options.signingKey);
38
+ // Normalized for the same reason `audience` is, and it has to happen before
39
+ // both uses: `jku` names only the origin, so an `issuer` carrying a trailing
40
+ // slash or a path would sign an `iss` that disagrees with it — and a verifier
41
+ // comparing `iss` byte-for-byte against a normalized origin allowlist rejects
42
+ // the token even though the two URLs share an origin.
43
+ const issuer = new URL(options.issuer).origin;
44
+ return new SignJWT({
45
+ [IDENTITY_CLAIM]: options.identity,
46
+ [TENANT_CLAIM]: options.tenant
47
+ })
48
+ .setProtectedHeader({
49
+ alg: A2A_JWS_ALG,
50
+ kid,
51
+ // Where the far side fetches the public half. A verifier must check this
52
+ // origin against its own allowlist *before* fetching, which is what stops
53
+ // a forged token nominating an attacker-controlled JWKS.
54
+ jku: jwksUrl(issuer)
55
+ })
56
+ .setIssuer(issuer)
57
+ .setAudience(new URL(options.audience).origin)
58
+ .setIssuedAt()
59
+ .setExpirationTime(`${options.ttlSeconds ?? DEFAULT_TTL_SECONDS}s`)
60
+ .sign(key);
61
+ }
@@ -0,0 +1,49 @@
1
+ import type { WorkflowStep } from "cloudflare:workers";
2
+ import { type TurnPushContext } from "./push.js";
3
+ import type { PlainTask } from "./task.js";
4
+ /** What {@link deliverTerminalTask} needs to finish a turn. */
5
+ export interface DeliverTerminalOptions {
6
+ /** Which Task this is about, and where its callback goes. */
7
+ push: TurnPushContext;
8
+ /** The agent's card-signing key, for the callback JWT. */
9
+ signingKey: string;
10
+ /**
11
+ * The guarded write, and **the cancellation check**. Return whether it
12
+ * applied: `false` must mean the row was already terminal (canceled), which is
13
+ * what suppresses the callback.
14
+ *
15
+ * Resolve any Durable Object stub *inside* this function, never above it — a
16
+ * stub hoisted out of a step body is a live connection that a replay cannot
17
+ * reconnect.
18
+ */
19
+ saveTask: (task: PlainTask) => Promise<boolean>;
20
+ /**
21
+ * Build the terminal Task. Called **inside** the `complete` step so that what
22
+ * gets notified is exactly what was persisted: building it outside would
23
+ * re-stamp `new Date()` on every replay and post a Task that differs from the
24
+ * stored one.
25
+ */
26
+ terminal: () => PlainTask;
27
+ /**
28
+ * Optional best-effort cleanup, run after the write and before the callback.
29
+ * An agent with no managed children omits it and no `sweep` step appears.
30
+ */
31
+ sweep?: () => Promise<void>;
32
+ }
33
+ /**
34
+ * Persist a turn's terminal Task, then notify the gateway.
35
+ *
36
+ * **The guarded write is the cancellation check.** `saveTask` refuses to write a
37
+ * terminal state over a `canceled` row and says so, doing that read and write in
38
+ * one synchronous pass inside the Durable Object. Probing first and saving
39
+ * second would leave a window — between the two calls, and again between this
40
+ * step and `notify` — in which a `tasks/cancel` lands and the gateway still
41
+ * receives a `completed` callback. Keying the notify on "did the write apply"
42
+ * closes it, and that has already been got wrong once.
43
+ *
44
+ * In `/a2a` rather than `/round` because the shape is not a round's: an agent
45
+ * whose turn is a single inference ends it the same way, and importing this from
46
+ * `/round` would put the whole delegation engine in a bundle that must not carry
47
+ * it. Everything it calls already lives here.
48
+ */
49
+ export declare function deliverTerminalTask(step: WorkflowStep, options: DeliverTerminalOptions): Promise<void>;
@@ -0,0 +1,49 @@
1
+ import { createPushChannel } from "./push.js";
2
+ /**
3
+ * Persist a turn's terminal Task, then notify the gateway.
4
+ *
5
+ * **The guarded write is the cancellation check.** `saveTask` refuses to write a
6
+ * terminal state over a `canceled` row and says so, doing that read and write in
7
+ * one synchronous pass inside the Durable Object. Probing first and saving
8
+ * second would leave a window — between the two calls, and again between this
9
+ * step and `notify` — in which a `tasks/cancel` lands and the gateway still
10
+ * receives a `completed` callback. Keying the notify on "did the write apply"
11
+ * closes it, and that has already been got wrong once.
12
+ *
13
+ * In `/a2a` rather than `/round` because the shape is not a round's: an agent
14
+ * whose turn is a single inference ends it the same way, and importing this from
15
+ * `/round` would put the whole delegation engine in a bundle that must not carry
16
+ * it. Everything it calls already lives here.
17
+ */
18
+ export async function deliverTerminalTask(step, options) {
19
+ const task = await step.do("complete", async () => {
20
+ const terminal = options.terminal();
21
+ return (await options.saveTask(terminal)) ? terminal : null;
22
+ });
23
+ if (!task)
24
+ return;
25
+ // Caught, not left to propagate: the terminal Task is already durably saved,
26
+ // so a sweep that still fails once the step's own retries are exhausted must
27
+ // not block the callback — the gateway is owed its result either way.
28
+ if (options.sweep) {
29
+ const sweep = options.sweep;
30
+ try {
31
+ await step.do("sweep", async () => {
32
+ await sweep();
33
+ });
34
+ }
35
+ catch (err) {
36
+ console.error("[deliver] sweep failed after retries", {
37
+ taskId: options.push.taskId,
38
+ err: String(err)
39
+ });
40
+ }
41
+ }
42
+ // A card-key-signed callback POST. Retried by the step on a non-2xx; the
43
+ // terminal messageId is deterministic and the gateway is idempotent and
44
+ // single-use, so retries are safe. If it ultimately fails, the gateway's own
45
+ // reaction backstop clears the pending marker.
46
+ await step.do("notify", async () => {
47
+ await createPushChannel(options.signingKey, options.push).deliver(task);
48
+ });
49
+ }
@@ -22,6 +22,8 @@ export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-p
22
22
  export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken, type GatewayIdentity, type VerifyOptions } from "./verify.js";
23
23
  export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks, type AgentManifest, type BuildCardOptions, type CardSigningConfig, type WireAgentCard } from "./card.js";
24
24
  export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
25
+ export { deliverTerminalTask, type DeliverTerminalOptions } from "./deliver.js";
26
+ export { signCallerToken, type CallerTokenOptions } from "./caller-token.js";
25
27
  export { callerContext } from "./caller.js";
26
28
  export { createPushChannel, type PushChannel, type TurnPushContext } from "./push.js";
27
29
  export { taskStateLabel, type PlainArtifact, type PlainMessage, type PlainPart, type PlainStatus, type PlainTask } from "./task.js";
package/dist/a2a/index.js CHANGED
@@ -22,6 +22,8 @@ export { A2A_JWS_ALG, audienceFor, endpointUrl, jwksUrl } from "@loopingai/a2a-p
22
22
  export { IDENTITY_CLAIM, TENANT_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken } from "./verify.js";
23
23
  export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks } from "./card.js";
24
24
  export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
25
+ export { deliverTerminalTask } from "./deliver.js";
26
+ export { signCallerToken } from "./caller-token.js";
25
27
  export { callerContext } from "./caller.js";
26
28
  export { createPushChannel } from "./push.js";
27
29
  export { taskStateLabel } from "./task.js";
@@ -37,9 +37,10 @@ export { NOTIFICATION_TOKEN_HEADER } from "@loopingai/a2a-protocol";
37
37
  */
38
38
  export declare function buildSubmittedTask(taskId: string, contextId: string): PlainTask;
39
39
  /**
40
- * The terminal `completed` Task for a turn the agent deliberately did not answer
41
- * (it called the `no_reply` tool see the agent loop). Same
42
- * shape as {@link buildSubmittedTask}: **no `status.message` at all**.
40
+ * The terminal `completed` Task for a turn the agent deliberately did not
41
+ * answer a turn an `AgentPlugin.shouldHandleTurn` gate declined, or whatever
42
+ * else a host treats as "nothing to say". Same shape as
43
+ * {@link buildSubmittedTask}: **no `status.message` at all**.
43
44
  *
44
45
  * The callback is still POSTed. The gateway's pending row has to resolve — we
45
46
  * simply hand it nothing to post to Slack. There is no `messageId` because there
@@ -69,9 +69,10 @@ export function buildSubmittedTask(taskId, contextId) {
69
69
  return buildBareTask(taskId, contextId, TaskState.TASK_STATE_SUBMITTED);
70
70
  }
71
71
  /**
72
- * The terminal `completed` Task for a turn the agent deliberately did not answer
73
- * (it called the `no_reply` tool see the agent loop). Same
74
- * shape as {@link buildSubmittedTask}: **no `status.message` at all**.
72
+ * The terminal `completed` Task for a turn the agent deliberately did not
73
+ * answer a turn an `AgentPlugin.shouldHandleTurn` gate declined, or whatever
74
+ * else a host treats as "nothing to say". Same shape as
75
+ * {@link buildSubmittedTask}: **no `status.message` at all**.
75
76
  *
76
77
  * The callback is still POSTed. The gateway's pending row has to resolve — we
77
78
  * simply hand it nothing to post to Slack. There is no `messageId` because there
@@ -0,0 +1,15 @@
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";
@@ -0,0 +1,19 @@
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";
@@ -0,0 +1,59 @@
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;