content-agent 0.8.3 → 1.0.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 +16 -29
- package/dist/_chunks-dts/sanity-agent-provider.d.ts +1 -4
- package/dist/_chunks-dts/sanity-agent-provider.d.ts.map +1 -1
- package/dist/content-agent.d.ts +5 -7
- package/dist/content-agent.d.ts.map +1 -1
- package/dist/content-agent.js +2 -5
- package/dist/content-agent.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -35,9 +35,9 @@ const { text } = await agent.generate({
|
|
|
35
35
|
|
|
36
36
|
> **Why a class instead of `streamText({ model, ... })`?**
|
|
37
37
|
>
|
|
38
|
-
> The AI SDK 6 explicitly invites SDK authors to ship their own `Agent` implementations: ["The `Agent` is now an interface instead of a class, which allows developers to implement their own agent abstractions for specific needs."](https://ai-sdk.dev/blog/ai-sdk-6) `ContentAgent` is that implementation for the Sanity Content Agent — it preconfigures `stopWhen`, `prepareStep`, and `toolChoice` for the
|
|
38
|
+
> The AI SDK 6 explicitly invites SDK authors to ship their own `Agent` implementations: ["The `Agent` is now an interface instead of a class, which allows developers to implement their own agent abstractions for specific needs."](https://ai-sdk.dev/blog/ai-sdk-6) `ContentAgent` is that implementation for the Sanity Content Agent — it preconfigures `stopWhen`, `prepareStep`, and `toolChoice` for the `final_answer` reply pipeline so you don't have to.
|
|
39
39
|
|
|
40
|
-
> **Migrating from `streamText({ model: provider.agent(threadId) })`?** Nothing breaks — the lower-level API is still supported and works exactly as before.
|
|
40
|
+
> **Migrating from `streamText({ model: provider.agent(threadId) })`?** Nothing breaks — the lower-level API is still supported and works exactly as before. Switch to `ContentAgent` to pick up the safe defaults that prevent empty responses when the model burns its step budget on client tool calls. See [How replies work](#how-replies-work) below.
|
|
41
41
|
|
|
42
42
|
## Streaming
|
|
43
43
|
|
|
@@ -87,28 +87,17 @@ const result = await agent.stream({
|
|
|
87
87
|
});
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
##
|
|
90
|
+
## How replies work
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
Every reply is delivered through a server-side `final_answer` tool call. The agent streams `final_answer.message` to the wire as `text-delta` chunks (parsed incrementally from the tool-call args) and includes structured `suggestions` for clients that render reply buttons. There's no separate "streaming mode" — every conversation, every client follows the same path.
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
const agent = new ContentAgent({
|
|
96
|
-
provider,
|
|
97
|
-
threadId: "my-thread",
|
|
98
|
-
tools: { update_issue_status, get_settings },
|
|
99
|
-
config: {
|
|
100
|
-
responseMode: "tool",
|
|
101
|
-
enableReplySuggestions: true,
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
In this mode `ContentAgent` adjusts the [loop control](https://ai-sdk.dev/docs/agents/loop-control) primitives to make the agent safe by default:
|
|
94
|
+
`ContentAgent` adjusts the [loop control](https://ai-sdk.dev/docs/agents/loop-control) primitives to make this safe by default:
|
|
107
95
|
|
|
108
|
-
1. **[`stopWhen`](https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent#stopwhen)** defaults to "stop
|
|
109
|
-
2. **[`prepareStep`](https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent#preparestep)** is composed with a safety net that, on the cap step, forces [`toolChoice`](https://ai-sdk.dev/docs/agents/building-agents#tool-choice) `{ type: 'tool', toolName: 'final_answer' }` if the model has been calling tools without ever converging on `final_answer`. This guarantees
|
|
96
|
+
1. **[`stopWhen`](https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent#stopwhen)** defaults to "stop on the first step that contains a `final_answer` tool call," plus a [`stepCountIs(20)`](https://ai-sdk.dev/docs/reference/ai-sdk-core/step-count-is) safety cap. A `final_answer` accompanied by other tool calls in the same step is the canonical act-and-answer pattern — the loop terminates without an extra round-trip.
|
|
97
|
+
2. **[`prepareStep`](https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent#preparestep)** is composed with a safety net that, on the cap step, forces [`toolChoice`](https://ai-sdk.dev/docs/agents/building-agents#tool-choice) `{ type: 'tool', toolName: 'final_answer' }` if the model has been calling tools without ever converging on `final_answer`. This guarantees a reply on the wire even in pathological cases where the model would otherwise burn its step budget on client-tool round-trips.
|
|
98
|
+
3. **Server-side recovery (SAGE-248)** — if the model fails to call `final_answer` naturally on any single turn, the agent runner retries that turn with forced `tool_choice`. Anthropic's API enforces forced tool choice strictly; the recovery is guaranteed to produce a `final_answer` chunk.
|
|
110
99
|
|
|
111
|
-
If you pass your own `stopWhen` or `prepareStep`, they take precedence — `ContentAgent` doesn't reach into them.
|
|
100
|
+
If you pass your own `stopWhen` or `prepareStep`, they take precedence — `ContentAgent` doesn't reach into them.
|
|
112
101
|
|
|
113
102
|
## Applications
|
|
114
103
|
|
|
@@ -165,15 +154,13 @@ const agent = new ContentAgent({
|
|
|
165
154
|
|
|
166
155
|
### Config fields
|
|
167
156
|
|
|
168
|
-
| Field
|
|
169
|
-
|
|
|
170
|
-
| `instruction`
|
|
171
|
-
| `userMessageContext`
|
|
172
|
-
| `capabilities`
|
|
173
|
-
| `filter`
|
|
174
|
-
| `perspectives`
|
|
175
|
-
| `responseMode` | `'stream' \| 'tool'` | Set to `'tool'` to deliver responses through the `final_answer` tool |
|
|
176
|
-
| `enableReplySuggestions` | `boolean` | When `responseMode: 'tool'`, include reply-suggestion buttons on output |
|
|
157
|
+
| Field | Type | Description |
|
|
158
|
+
| -------------------- | ------------------------ | ---------------------------------------------------------------- |
|
|
159
|
+
| `instruction` | `string` | Custom instruction included in the system prompt |
|
|
160
|
+
| `userMessageContext` | `Record<string, string>` | Key-value pairs appended to each user message as XML tags |
|
|
161
|
+
| `capabilities` | `object` | Controls what operations the agent can perform (`read`, `write`) |
|
|
162
|
+
| `filter` | `object` | GROQ boolean expressions for document access control |
|
|
163
|
+
| `perspectives` | `object` | Perspective locking for read and write operations |
|
|
177
164
|
|
|
178
165
|
### Capabilities
|
|
179
166
|
|
|
@@ -490,10 +490,7 @@ type ApplicationResource = NonNullable<Application['resource']>;
|
|
|
490
490
|
* }
|
|
491
491
|
* ```
|
|
492
492
|
*/
|
|
493
|
-
type Config = components['schemas']['Config']
|
|
494
|
-
/** Controls how the agent delivers responses. 'tool' enables the final_answer tool with structured message + suggestions. Omit or use 'stream' for normal streaming. */responseMode?: 'stream' | 'tool'; /** When true and responseMode is 'tool', the final_answer tool includes a suggestions field for follow-up reply buttons. */
|
|
495
|
-
enableReplySuggestions?: boolean;
|
|
496
|
-
};
|
|
493
|
+
type Config = components['schemas']['Config'];
|
|
497
494
|
/**
|
|
498
495
|
* Controls what operations the agent can perform.
|
|
499
496
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanity-agent-provider.d.ts","names":[],"sources":["../../src/generated/openapi.ts","../../src/types.ts","../../src/from-client.ts","../../src/sanity-agent-provider.ts"],"mappings":";;;AAKA;;KAAY,KAAA;EACV,sCAAA;IACE,UAAA;MACE,KAAA;MACA,MAAA;MACA,IAAA;MACA,MAAA;IAAA;IAEF,GAAA;IACA,GAAA;IAMA;;;;;IAAA,IAAA,EAAM,UAAA;IACN,MAAA;IACA,OAAA;IACA,IAAA;IACA,KAAA;IACA,KAAA;EAAA;EAEF,SAAA;IACE,UAAA;MACE,KAAA;MACA,MAAA;MACA,IAAA;MACA,MAAA;IAAA;IAEF,GAAA;IACA,GAAA;IAWA;;;AAIJ;;IATI,IAAA,EAAM,UAAA;IACN,MAAA;IACA,OAAA;IACA,IAAA;IACA,KAAA;IACA,KAAA;EAAA;AAAA;AAAA,KAIQ,UAAA;EACV,OAAA;IACE,WAAA;MAuNW,4CArNT,QAAA,EAAU,UAAA;MACV,WAAA,GAAc,UAAA;MACd,MAAA,GAAS,UAAA;MA6RuB;;;;MAxRhC,MAAA;MAPU;;;;;;;MAeV,MAAA;IAAA;IAEF,aAAA;MAWE;;;;MANA,cAAA;MA0BA;;;;MArBA,OAAA;MACA,MAAA,GAAS,UAAA;MAmCT;;;;MA9BA,YAAA;MAmDA;;;;;;;MA3CA,MAAA;IAAA;IAEF,OAAA;MAmEW;;;;MA9DT,IAAA,mCA0EO;MAxEP,OAAA,UAyEQ;MAvER,EAAA,WAwEW;MAtEX,IAAA;IAAA,GAyFF;IAtFA,WAAA;MAuFA;;;;MAlFE,GAAA;MACA,QAAA;QA2HA;;;;QAtHE,EAAA;QAqIJ;;;;QAhII,IAAA;MAAA;MAmJF;;;;;MA5IA,aAAA,WA+JG;MA7JH,aAAA,WA+KE;MA7KF,IAAA,WAiLF;MA/KE,KAAA,WAiLG;MA/KH,kBAAA;IAAA,GAwLsB;IArLxB,MAAA;MA0LE;;;;MArLA,WAAA;MAmMJ;;;;;;AAMF;;;MA/LM,kBAAA;QAAA,CACG,GAAA;MAAA;MAEH,YAAA,GAAe,UAAA;MACf,MAAA,GAAS,UAAA;MACT,YAAA,GAAe,UAAA;IAAA;IA8PV;;;;;;;;IApPP,YAAA;MACE,IAAA,GAAO,UAAA;MACP,KAAA,GAAQ,UAAA;MACR,QAAA,GAAW,UAAA;IAAA;IAiMX;;;;IA3LF,gBAAA;MAkMI;;;;MA7LF,SAAA;IAAA;IAqMI;;;;;;IA7LN,UAAA,YAAsB,UAAA;IACtB,gBAAA;MAoMF;;;;;;;;;;MAzLI,MAAA;IAAA,GAwME;IArMJ,MAAA;MAuMM;;;;;MAjMJ,IAAA;MAyMA;;;;;MAnMA,KAAA;IAAA;ICrNM;;;;;ID4NR,YAAA;MCvNuB;;;;AAM3B;;;;MD0NM,IAAA;MClNM;;;;;MDwNN,KAAA;IAAA,GCnNsB;IDsNxB,YAAA;MCtNkC,6CDwNhC,IAAA,WCnNqB;MDqNrB,cAAA;IAAA;IAEF,aAAA;MC/MQ;;;;MDoNN,KAAA;MC/MM;;;;MDoNN,OAAA,WCpMM;MDsMN,MAAA,GAAS,UAAA;IAAA;IAEX,eAAA;MCxMgC;AAKpC;;;MDwMM,IAAA;MCxMmD;AAqCzD;;;;;MD0KM,IAAA;
|
|
1
|
+
{"version":3,"file":"sanity-agent-provider.d.ts","names":[],"sources":["../../src/generated/openapi.ts","../../src/types.ts","../../src/from-client.ts","../../src/sanity-agent-provider.ts"],"mappings":"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"}
|
package/dist/content-agent.d.ts
CHANGED
|
@@ -19,10 +19,10 @@ type ContentAgentSettings<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet,
|
|
|
19
19
|
* abstraction. Construction is the only thing that differs: pass a `provider`
|
|
20
20
|
* and `threadId` instead of a `model`.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* the wire
|
|
25
|
-
*
|
|
22
|
+
* Every conversation runs in the unified tool-response flow: the model's
|
|
23
|
+
* user-visible reply is delivered through the server-side `final_answer`
|
|
24
|
+
* tool, and the wire streams its `message` field as text-deltas. To make
|
|
25
|
+
* that flow safe by default, `ContentAgent`:
|
|
26
26
|
*
|
|
27
27
|
* 1. Defaults `stopWhen` to "stop on the first step that contains a
|
|
28
28
|
* `final_answer` tool call, or after 20 steps." This lets the model land
|
|
@@ -34,8 +34,7 @@ type ContentAgentSettings<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet,
|
|
|
34
34
|
* has been calling tools but hasn't yet converged.
|
|
35
35
|
*
|
|
36
36
|
* Both defaults are skipped when the consumer passes their own `stopWhen` —
|
|
37
|
-
* they own the loop semantics
|
|
38
|
-
* plain `ToolLoopAgent` with no overrides.
|
|
37
|
+
* they own the loop semantics in that case.
|
|
39
38
|
*
|
|
40
39
|
* @example
|
|
41
40
|
* ```ts
|
|
@@ -48,7 +47,6 @@ type ContentAgentSettings<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet,
|
|
|
48
47
|
* provider,
|
|
49
48
|
* threadId: 'my-thread',
|
|
50
49
|
* tools: { update_issue_status, get_settings },
|
|
51
|
-
* config: { responseMode: 'tool' },
|
|
52
50
|
* })
|
|
53
51
|
*
|
|
54
52
|
* const result = await agent.stream({ messages })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-agent.d.ts","names":[],"sources":["../src/content-agent.ts"],"mappings":";;AAY4C;AAAA,KAMvC,yBAAA,GAA4B,IAAA,CAC/B,aAAA;;;;AASF;;KAAY,oBAAA,qCAEI,OAAA,GAAU,OAAA,iBACT,MAAA,CAAO,MAAA,YACpB,IAAA,CAAK,qBAAA,CAAsB,YAAA,EAAc,KAAA,EAAO,MAAA,cAClD,yBAAA;EAHc,wEAKZ,QAAA,EAAU,oBAAA,EAJG;EAMb,QAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"content-agent.d.ts","names":[],"sources":["../src/content-agent.ts"],"mappings":";;AAY4C;AAAA,KAMvC,yBAAA,GAA4B,IAAA,CAC/B,aAAA;;;;AASF;;KAAY,oBAAA,qCAEI,OAAA,GAAU,OAAA,iBACT,MAAA,CAAO,MAAA,YACpB,IAAA,CAAK,qBAAA,CAAsB,YAAA,EAAc,KAAA,EAAO,MAAA,cAClD,yBAAA;EAHc,wEAKZ,QAAA,EAAU,oBAAA,EAJG;EAMb,QAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AAkHJ;;;;;;;;;;;;;;;;;;;cAAa,YAAA,qCAEG,OAAA,GAAU,OAAA,iBACT,MAAA,CAAO,MAAA,kBACd,aAAA,CAAc,YAAA,EAAc,KAAA,EAAO,MAAA;cAC/B,QAAA,EAAU,oBAAA,CAAqB,YAAA,EAAc,KAAA,EAAO,MAAA;AAAA"}
|
package/dist/content-agent.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { ToolLoopAgent, stepCountIs } from "ai";
|
|
2
2
|
import { pickBy, isNotNil } from "es-toolkit";
|
|
3
3
|
const DEFAULT_MAX_STEPS = 20, FINAL_ANSWER_TOOL_NAME = "final_answer";
|
|
4
|
-
function isToolResponseMode(settings) {
|
|
5
|
-
return settings.config?.responseMode === "tool";
|
|
6
|
-
}
|
|
7
4
|
function hasFinalAnswer() {
|
|
8
5
|
return ({ steps }) => {
|
|
9
6
|
const lastStep = steps.at(-1);
|
|
10
7
|
return lastStep?.toolCalls?.length ? lastStep.toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME) : !1;
|
|
11
8
|
};
|
|
12
9
|
}
|
|
13
|
-
function
|
|
10
|
+
function defaultStopWhen() {
|
|
14
11
|
return [hasFinalAnswer(), stepCountIs(DEFAULT_MAX_STEPS)];
|
|
15
12
|
}
|
|
16
13
|
const FORCE_FINAL_ANSWER = {
|
|
@@ -41,7 +38,7 @@ class ContentAgent extends ToolLoopAgent {
|
|
|
41
38
|
} = settings, modelSettings = pickBy(
|
|
42
39
|
{ application, config, format, clientId, onStreamData },
|
|
43
40
|
isNotNil
|
|
44
|
-
), model = provider.agent(threadId, modelSettings), useDefaults = stopWhen === void 0
|
|
41
|
+
), model = provider.agent(threadId, modelSettings), useDefaults = stopWhen === void 0, resolvedStopWhen = useDefaults ? defaultStopWhen() : stopWhen, resolvedPrepareStep = useDefaults ? buildSafetyNetPrepareStep(DEFAULT_MAX_STEPS, prepareStep) : prepareStep;
|
|
45
42
|
super({
|
|
46
43
|
...toolLoopSettings,
|
|
47
44
|
model,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-agent.js","sources":["../src/content-agent.ts"],"sourcesContent":["import {\n type Output,\n type PrepareStepFunction,\n type StopCondition,\n stepCountIs,\n type ToolChoice,\n ToolLoopAgent,\n type ToolLoopAgentSettings,\n type ToolSet,\n} from 'ai'\nimport { isNotNil, pickBy } from 'es-toolkit'\nimport type { ContentAgentProvider } from './sanity-agent-provider'\nimport type { AgentSettings } from './types'\n\nconst DEFAULT_MAX_STEPS = 20\nconst FINAL_ANSWER_TOOL_NAME = 'final_answer'\n\n/** Sanity-specific settings forwarded to `provider.agent(threadId, ...)`. */\ntype ContentAgentModelSettings = Pick<\n AgentSettings,\n 'application' | 'config' | 'format' | 'clientId' | 'onStreamData'\n>\n\n/**\n * Settings accepted by the {@link ContentAgent} constructor — everything\n * `ToolLoopAgent` accepts, minus `model` (resolved from the provider) and\n * plus the Sanity-specific routing fields.\n */\nexport type ContentAgentSettings<\n CALL_OPTIONS = never,\n TOOLS extends ToolSet = ToolSet,\n OUTPUT extends Output.Output = never,\n> = Omit<ToolLoopAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>, 'model'> &\n ContentAgentModelSettings & {\n /** Provider returned by `createContentAgent` or `createStudioAgent`. */\n provider: ContentAgentProvider\n /** Conversation thread ID. Reusing it continues an existing conversation. */\n threadId: string\n }\n\
|
|
1
|
+
{"version":3,"file":"content-agent.js","sources":["../src/content-agent.ts"],"sourcesContent":["import {\n type Output,\n type PrepareStepFunction,\n type StopCondition,\n stepCountIs,\n type ToolChoice,\n ToolLoopAgent,\n type ToolLoopAgentSettings,\n type ToolSet,\n} from 'ai'\nimport { isNotNil, pickBy } from 'es-toolkit'\nimport type { ContentAgentProvider } from './sanity-agent-provider'\nimport type { AgentSettings } from './types'\n\nconst DEFAULT_MAX_STEPS = 20\nconst FINAL_ANSWER_TOOL_NAME = 'final_answer'\n\n/** Sanity-specific settings forwarded to `provider.agent(threadId, ...)`. */\ntype ContentAgentModelSettings = Pick<\n AgentSettings,\n 'application' | 'config' | 'format' | 'clientId' | 'onStreamData'\n>\n\n/**\n * Settings accepted by the {@link ContentAgent} constructor — everything\n * `ToolLoopAgent` accepts, minus `model` (resolved from the provider) and\n * plus the Sanity-specific routing fields.\n */\nexport type ContentAgentSettings<\n CALL_OPTIONS = never,\n TOOLS extends ToolSet = ToolSet,\n OUTPUT extends Output.Output = never,\n> = Omit<ToolLoopAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>, 'model'> &\n ContentAgentModelSettings & {\n /** Provider returned by `createContentAgent` or `createStudioAgent`. */\n provider: ContentAgentProvider\n /** Conversation thread ID. Reusing it continues an existing conversation. */\n threadId: string\n }\n\n/**\n * Stop when the model's last step contains `final_answer`, regardless of\n * what other tool calls are alongside it.\n *\n * Before SAGE-182's Phase 1 (which removed the SSE `providerExecuted`\n * filter on the consumer side) the consumer's `step.toolCalls` never\n * actually contained `final_answer` — it was stripped at the wire — so\n * a stricter \"final_answer alone\" condition was the only thing that\n * could ever fire, and only after the model emitted a solo `final_answer`\n * step *following* the act-and-answer step. That cost an extra LLM\n * round-trip per turn and was the root of the SAGE-180 latency bug.\n *\n * With the filter gone, the canonical act-and-answer step\n * (`[update_issue_status, final_answer]`) is the natural terminator.\n * Stopping on any presence of `final_answer` lets the loop end on the\n * step the model actually finishes, with no wasted round-trip.\n */\nfunction hasFinalAnswer<TOOLS extends ToolSet>(): StopCondition<TOOLS> {\n return ({ steps }) => {\n const lastStep = steps.at(-1)\n if (!lastStep?.toolCalls?.length) return false\n return lastStep.toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME)\n }\n}\n\nfunction defaultStopWhen<TOOLS extends ToolSet>(): Array<StopCondition<TOOLS>> {\n return [hasFinalAnswer<TOOLS>(), stepCountIs(DEFAULT_MAX_STEPS)]\n}\n\n/**\n * `final_answer` is a server-side tool — not in the consumer's typed `tools` map.\n * Hoisted as a constant so the cast lives in one place.\n */\nconst FORCE_FINAL_ANSWER = {\n type: 'tool',\n toolName: FINAL_ANSWER_TOOL_NAME,\n} satisfies ToolChoice<ToolSet>\n\n/**\n * Forces `final_answer` on the cap step when the model has been calling tools\n * without ever converging on `final_answer`. Composes with a user-provided\n * `prepareStep` — theirs runs first, the override is overlaid.\n */\nfunction buildSafetyNetPrepareStep<TOOLS extends ToolSet>(\n maxSteps: number,\n userPrepareStep: PrepareStepFunction<TOOLS> | undefined,\n): PrepareStepFunction<TOOLS> {\n return async (options) => {\n const userResult = await userPrepareStep?.(options)\n if (options.stepNumber !== maxSteps - 1) return userResult\n\n const toolCalls = options.steps.flatMap((step) => step.toolCalls ?? [])\n if (toolCalls.length === 0) return userResult\n if (toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME)) return userResult\n\n // Anthropic rejects `tool_choice` when the conversation's last message is\n // an assistant turn (the \"no assistant message prefill\" rule). That can\n // happen when a prior server-side tool call hasn't been paired with its\n // result in the consumer's view yet. In that case, skip the override —\n // the model gets its normal final step. Worst case we're back to the\n // pre-fix behaviour (possibly empty response), but no crash.\n if (options.messages.at(-1)?.role === 'assistant') return userResult\n\n return { ...userResult, toolChoice: FORCE_FINAL_ANSWER as ToolChoice<TOOLS> }\n }\n}\n\n/**\n * Sanity Content Agent wrapped as an `ai` SDK agent.\n *\n * Extends {@link ToolLoopAgent} so it inherits `.generate()`, `.stream()`,\n * telemetry, and every other capability of the AI SDK's first-class agent\n * abstraction. Construction is the only thing that differs: pass a `provider`\n * and `threadId` instead of a `model`.\n *\n * Every conversation runs in the unified tool-response flow: the model's\n * user-visible reply is delivered through the server-side `final_answer`\n * tool, and the wire streams its `message` field as text-deltas. To make\n * that flow safe by default, `ContentAgent`:\n *\n * 1. Defaults `stopWhen` to \"stop on the first step that contains a\n * `final_answer` tool call, or after 20 steps.\" This lets the model land\n * its terminal `final_answer` together with client tools in the same\n * step (the act-and-answer pattern) without burning an extra LLM\n * round-trip on a solo `final_answer` follow-up.\n * 2. Composes a `prepareStep` safety net that, on the cap step, forces\n * `toolChoice: { type: 'tool', toolName: 'final_answer' }` when the model\n * has been calling tools but hasn't yet converged.\n *\n * Both defaults are skipped when the consumer passes their own `stopWhen` —\n * they own the loop semantics in that case.\n *\n * @example\n * ```ts\n * import { createContentAgent } from 'content-agent'\n * import { ContentAgent } from 'content-agent/agent'\n *\n * const provider = createContentAgent({ organizationId, token })\n *\n * const agent = new ContentAgent({\n * provider,\n * threadId: 'my-thread',\n * tools: { update_issue_status, get_settings },\n * })\n *\n * const result = await agent.stream({ messages })\n * for await (const chunk of result.textStream) process.stdout.write(chunk)\n * ```\n *\n * @category Agent\n */\nexport class ContentAgent<\n CALL_OPTIONS = never,\n TOOLS extends ToolSet = ToolSet,\n OUTPUT extends Output.Output = never,\n> extends ToolLoopAgent<CALL_OPTIONS, TOOLS, OUTPUT> {\n constructor(settings: ContentAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>) {\n const {\n provider,\n threadId,\n application,\n config,\n format,\n clientId,\n onStreamData,\n stopWhen,\n prepareStep,\n ...toolLoopSettings\n } = settings\n\n const modelSettings = pickBy(\n { application, config, format, clientId, onStreamData },\n isNotNil,\n ) as ContentAgentModelSettings\n const model = provider.agent(threadId, modelSettings)\n\n // Defaults are coupled: if the consumer brought their own `stopWhen` they\n // own the loop, so the safety-net `prepareStep` (which assumes a known\n // step cap) doesn't apply either.\n const useDefaults = stopWhen === undefined\n const resolvedStopWhen = useDefaults ? defaultStopWhen<TOOLS>() : stopWhen\n const resolvedPrepareStep = useDefaults\n ? buildSafetyNetPrepareStep<TOOLS>(DEFAULT_MAX_STEPS, prepareStep)\n : prepareStep\n\n super({\n ...toolLoopSettings,\n model,\n ...(resolvedStopWhen !== undefined && { stopWhen: resolvedStopWhen }),\n ...(resolvedPrepareStep !== undefined && { prepareStep: resolvedPrepareStep }),\n } as ToolLoopAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>)\n }\n}\n"],"names":[],"mappings":";;AAcA,MAAM,oBAAoB,IACpB,yBAAyB;AA0C/B,SAAS,iBAA8D;AACrE,SAAO,CAAC,EAAE,YAAY;AACpB,UAAM,WAAW,MAAM,GAAG,EAAE;AAC5B,WAAK,UAAU,WAAW,SACnB,SAAS,UAAU,KAAK,CAAC,SAAS,KAAK,aAAa,sBAAsB,IADxC;AAAA,EAE3C;AACF;AAEA,SAAS,kBAAsE;AAC7E,SAAO,CAAC,eAAA,GAAyB,YAAY,iBAAiB,CAAC;AACjE;AAMA,MAAM,qBAAqB;AAAA,EACzB,MAAM;AAAA,EACN,UAAU;AACZ;AAOA,SAAS,0BACP,UACA,iBAC4B;AAC5B,SAAO,OAAO,YAAY;AACxB,UAAM,aAAa,MAAM,kBAAkB,OAAO;AAClD,QAAI,QAAQ,eAAe,WAAW,EAAG,QAAO;AAEhD,UAAM,YAAY,QAAQ,MAAM,QAAQ,CAAC,SAAS,KAAK,aAAa,EAAE;AAUtE,WATI,UAAU,WAAW,KACrB,UAAU,KAAK,CAAC,SAAS,KAAK,aAAa,sBAAsB,KAQjE,QAAQ,SAAS,GAAG,EAAE,GAAG,SAAS,cAAoB,aAEnD,EAAE,GAAG,YAAY,YAAY,mBAAA;AAAA,EACtC;AACF;AA8CO,MAAM,qBAIH,cAA2C;AAAA,EACnD,YAAY,UAA6D;AACvE,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IAAA,IACD,UAEE,gBAAgB;AAAA,MACpB,EAAE,aAAa,QAAQ,QAAQ,UAAU,aAAA;AAAA,MACzC;AAAA,IAAA,GAEI,QAAQ,SAAS,MAAM,UAAU,aAAa,GAK9C,cAAc,aAAa,QAC3B,mBAAmB,cAAc,gBAAA,IAA2B,UAC5D,sBAAsB,cACxB,0BAAiC,mBAAmB,WAAW,IAC/D;AAEJ,UAAM;AAAA,MACJ,GAAG;AAAA,MACH;AAAA,MACA,GAAI,qBAAqB,UAAa,EAAE,UAAU,iBAAA;AAAA,MAClD,GAAI,wBAAwB,UAAa,EAAE,aAAa,oBAAA;AAAA,IAAoB,CACvB;AAAA,EACzD;AACF;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "content-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Vercel AI SDK provider for Sanity Content Agent API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@ai-sdk/provider-utils": "^2.2.8",
|
|
35
35
|
"es-toolkit": "^1.44.0",
|
|
36
36
|
"zod": "^3.25.76",
|
|
37
|
-
"@sanity/agent-types": "0.1.
|
|
37
|
+
"@sanity/agent-types": "0.1.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@sanity/pkg-utils": "^10.4.1",
|