content-agent 0.8.0 → 0.8.2

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.
@@ -24,11 +24,11 @@ type ContentAgentSettings<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet,
24
24
  * the wire and delivers all output through a server-side `final_answer`
25
25
  * tool. To make that mode safe by default, `ContentAgent`:
26
26
  *
27
- * 1. Defaults `stopWhen` to "stop when the last step is a clean `final_answer`
28
- * call (no other tools alongside it), or after 20 steps." This handles the
29
- * model using `final_answer` for both real answers and mid-task narration:
30
- * a `final_answer` accompanied by other tool calls keeps the loop going,
31
- * a `final_answer` alone ends it.
27
+ * 1. Defaults `stopWhen` to "stop on the first step that contains a
28
+ * `final_answer` tool call, or after 20 steps." This lets the model land
29
+ * its terminal `final_answer` together with client tools in the same
30
+ * step (the act-and-answer pattern) without burning an extra LLM
31
+ * round-trip on a solo `final_answer` follow-up.
32
32
  * 2. Composes a `prepareStep` safety net that, on the cap step, forces
33
33
  * `toolChoice: { type: 'tool', toolName: 'final_answer' }` when the model
34
34
  * has been calling tools but hasn't yet converged.
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHJ;;;;;;;;;;;;;;;;;;;;;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"}
@@ -4,14 +4,14 @@ const DEFAULT_MAX_STEPS = 20, FINAL_ANSWER_TOOL_NAME = "final_answer";
4
4
  function isToolResponseMode(settings) {
5
5
  return settings.config?.responseMode === "tool";
6
6
  }
7
- function finalAnswerOnly() {
7
+ function hasFinalAnswer() {
8
8
  return ({ steps }) => {
9
9
  const lastStep = steps.at(-1);
10
- return lastStep?.toolCalls?.length ? lastStep.toolCalls.every((call) => call.toolName === FINAL_ANSWER_TOOL_NAME) : !1;
10
+ return lastStep?.toolCalls?.length ? lastStep.toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME) : !1;
11
11
  };
12
12
  }
13
13
  function defaultToolResponseStopWhen() {
14
- return [finalAnswerOnly(), stepCountIs(DEFAULT_MAX_STEPS)];
14
+ return [hasFinalAnswer(), stepCountIs(DEFAULT_MAX_STEPS)];
15
15
  }
16
16
  const FORCE_FINAL_ANSWER = {
17
17
  type: "tool",
@@ -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\nfunction isToolResponseMode(settings: ContentAgentModelSettings): boolean {\n return settings.config?.responseMode === 'tool'\n}\n\n/**\n * Stop when the model's last step was a clean `final_answer` (no other tools\n * in the same step), or when the safety cap is hit.\n *\n * Why \"clean\" rather than just \"any `final_answer`\": in tool-response mode\n * the model uses `final_answer` for both terminal answers AND mid-task\n * narration (\"Let me query the studios first...\"). When `final_answer` is\n * accompanied by other tool calls in the same step, the model is signalling\n * it has more work to do — the loop should continue. When `final_answer`\n * stands alone, the model is done.\n */\nfunction finalAnswerOnly<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.every((call) => call.toolName === FINAL_ANSWER_TOOL_NAME)\n }\n}\n\nfunction defaultToolResponseStopWhen<TOOLS extends ToolSet>(): Array<StopCondition<TOOLS>> {\n return [finalAnswerOnly<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 * In tool-response mode (`config.responseMode: 'tool'`, used by integrations\n * like Linear, Slack, and MS Teams) the agent suppresses raw model text on\n * the wire and delivers all output through a server-side `final_answer`\n * tool. To make that mode safe by default, `ContentAgent`:\n *\n * 1. Defaults `stopWhen` to \"stop when the last step is a clean `final_answer`\n * call (no other tools alongside it), or after 20 steps.\" This handles the\n * model using `final_answer` for both real answers and mid-task narration:\n * a `final_answer` accompanied by other tool calls keeps the loop going,\n * a `final_answer` alone ends it.\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. Outside tool-response mode the agent is a\n * plain `ToolLoopAgent` with no overrides.\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 * config: { responseMode: 'tool' },\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 && isToolResponseMode(modelSettings)\n const resolvedStopWhen = useDefaults ? defaultToolResponseStopWhen<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;AAyB/B,SAAS,mBAAmB,UAA8C;AACxE,SAAO,SAAS,QAAQ,iBAAiB;AAC3C;AAaA,SAAS,kBAA+D;AACtE,SAAO,CAAC,EAAE,YAAY;AACpB,UAAM,WAAW,MAAM,GAAG,EAAE;AAC5B,WAAK,UAAU,WAAW,SACnB,SAAS,UAAU,MAAM,CAAC,SAAS,KAAK,aAAa,sBAAsB,IADzC;AAAA,EAE3C;AACF;AAEA,SAAS,8BAAkF;AACzF,SAAO,CAAC,gBAAA,GAA0B,YAAY,iBAAiB,CAAC;AAClE;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;AAgDO,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,UAAa,mBAAmB,aAAa,GACxE,mBAAmB,cAAc,4BAAA,IAAuC,UACxE,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;"}
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\nfunction isToolResponseMode(settings: ContentAgentModelSettings): boolean {\n return settings.config?.responseMode === 'tool'\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 defaultToolResponseStopWhen<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 * In tool-response mode (`config.responseMode: 'tool'`, used by integrations\n * like Linear, Slack, and MS Teams) the agent suppresses raw model text on\n * the wire and delivers all output through a server-side `final_answer`\n * tool. To make that mode 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. Outside tool-response mode the agent is a\n * plain `ToolLoopAgent` with no overrides.\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 * config: { responseMode: 'tool' },\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 && isToolResponseMode(modelSettings)\n const resolvedStopWhen = useDefaults ? defaultToolResponseStopWhen<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;AAyB/B,SAAS,mBAAmB,UAA8C;AACxE,SAAO,SAAS,QAAQ,iBAAiB;AAC3C;AAmBA,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,8BAAkF;AACzF,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;AAgDO,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,UAAa,mBAAmB,aAAa,GACxE,mBAAmB,cAAc,4BAAA,IAAuC,UACxE,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;"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/sanity-agent-language-model.ts","../src/studio.ts","../src/utils.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/sanity-agent-language-model.ts","../src/studio.ts","../src/utils.ts"],"mappings":";;;;;AAyBA;UAAiB,uBAAA;EACf,QAAA;EACA,OAAA;EACA,UAAA;EACA,cAAA;EACA,OAAA,QAAe,MAAA;EACf,WAAA,GAAc,kBAAA;EACd,KAAA,UAAe,UAAA,CAAW,KAAA;AAAA;;;;;;;;;;;;AAoE5B;;;;;;;;;;;;;;;;;;;;;cAAa,iBAAA,YAA6B,eAAA;EAAA,SAC/B,oBAAA;EAAA,SACA,QAAA;EAAA,SACA,OAAA;EAAA,SACA,aAAA,EAAe,MAAA,SAAe,MAAA;EAAA,iBAEtB,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,QAAA;cAGf,QAAA,UACA,QAAA,EAAU,aAAA,cACV,MAAA,EAAQ,uBAAA;EAAA;;;;;;;;;EAAA,QAkBF,uBAAA;EAkJ+C;;;;EAAA,QAzD/C,kBAAA;EAAA,QAiBA,eAAA;EAAA,QAoBA,gBAAA;EAAA,QAgBA,kBAAA;EAIF,UAAA,CAAW,OAAA,EAAS,0BAAA,GAA6B,OAAA,CAAQ,6BAAA;EAsFyB;;ACnV1F;;EDmV0F,QA9C1E,wBAAA;EA8CR,QAAA,CAAS,OAAA,EAAS,0BAAA,GAA6B,OAAA,CAAQ,2BAAA;AAAA;;AAlV/D;;KCDY,kBAAA;EDMK,uDCJf,KAAA,UAAe,UAAA,CAAW,KAAA;EDMX;;;;ECAf,eAAA,GAAkB,IAAA,CAAK,4BAAA;AAAA;;;;;;;;;;;iBAiIT,iBAAA,CACd,MAAA,EAAQ,gBAAA,EACR,SAAA,UACA,OAAA,GAAU,kBAAA,GACT,oBAAA;;;AD5IH;;;iBEXgB,YAAA,CACd,aAAA,EAAe,MAAA,8BACf,iBAAA,GAAoB,MAAA,mBACnB,MAAA;;;;;;iBAgBa,WAAA,CAAY,OAAA,UAAiB,UAAA,UAAoB,QAAA;;;;;;;;cAWpD,gBAAA,SAAyB,KAAA;EAAA,SAC3B,MAAA;EAAA,SACA,WAAA;EAAA,SACA,IAAA,EAAM,OAAA,CAAQ,qBAAA;cAEX,MAAA,UAAgB,OAAA;EAAA,eAQb,SAAA;AAAA;;;;;;;;;;;cAmBJ,sBAAA,SAA+B,KAAA;EAAA,SACjC,IAAA;cAEG,OAAA,UAAiB,IAAA;AAAA;AAAA,cAOlB,qBAAA;AAAA,KAED,cAAA;EACN,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;EACA,IAAA;AAAA;;;;;;;;iBA4BU,kBAAA,CAAmB,KAAA,YAAiB,cAAA;AAAA,iBAiBpC,iBAAA,CAAkB,UAAA,EAAY,cAAA;AAAA,KAIlC,wBAAA;EACV,OAAA;AAAA;;;;;;;ADzHF;;iBCoIsB,wBAAA,CACpB,UAAA,EAAY,cAAA,EACZ,KAAA,WACA,cAAA,SAAuB,OAAA,CAAQ,wBAAA,IAC9B,OAAA;;;;;;;iBAwBa,oBAAA,CAAqB,KAAA;AAAA,UAUpB,YAAA;EACf,GAAA;EACA,IAAA;EACA,OAAA,EAAS,MAAA;EACT,WAAA,GAAc,kBAAA;EACd,MAAA,GAAS,WAAA;EACT,KAAA,UAAe,UAAA,CAAW,KAAA;AAAA;;;;;;;iBASN,QAAA,CAAS,OAAA,EAAS,YAAA,GAAe,OAAA,CAAQ,QAAA;;;;;;iBAwB/C,oBAAA,CACd,OAAA,WAAkB,KAAA;EAAQ,IAAA;EAAc,IAAA;AAAA;;;;;;iBAyC1B,eAAA,CAAgB,MAAA,uBAA6B,2BAAA;AAAA,KAiBjD,gBAAA;EACN,IAAA;EAAc,IAAA,EAAM,yBAAA;AAAA;EACpB,IAAA;EAAgB,YAAA,EAAc,2BAAA;AAAA;EAC9B,IAAA;EAAc,IAAA;EAAc,IAAA;AAAA;AA3PlC;;;;;;;;;;;;;;;;;;;;AAgCA;;;;AAhCA,iBAqRgB,eAAA,CAAgB,IAAA,UAAc,aAAA,WAAwB,gBAAA;;;;;;;iBAmIhD,qBAAA,CACpB,YAAA,EAAc,cAAA,CAAe,UAAA,IAC5B,OAAA;;;;;AA9WH;;;;;iBA6YgB,eAAA,CACd,YAAA,EAAc,cAAA,CAAe,UAAA,GAC7B,QAAA,EAAU,eAAA,IACV,MAAA,IAAU,IAAA,UAAc,IAAA,qBACvB,cAAA,CAAe,yBAAA"}
package/dist/index.js CHANGED
@@ -165,15 +165,29 @@ function parseStreamLine(line, textContentId) {
165
165
  case "text-end":
166
166
  return { kind: "part", part: { type: "text-end", id: part.id || textContentId } };
167
167
  case "tool-input-available": {
168
- if (part.providerExecuted) return null;
169
- const input = part.input;
168
+ const input = part.input, isServerExecuted = part.providerExecuted === !0;
170
169
  return {
171
170
  kind: "part",
172
171
  part: {
173
172
  type: "tool-call",
174
173
  toolCallId: part.toolCallId,
175
174
  toolName: part.toolName,
176
- input: typeof input == "string" ? input : JSON.stringify(input)
175
+ input: typeof input == "string" ? input : JSON.stringify(input),
176
+ ...isServerExecuted && { providerExecuted: !0, dynamic: !0 }
177
+ }
178
+ };
179
+ }
180
+ case "tool-output-available": {
181
+ if (part.providerExecuted !== !0) return null;
182
+ const result = part.output ?? {};
183
+ return {
184
+ kind: "part",
185
+ part: {
186
+ type: "tool-result",
187
+ toolCallId: part.toolCallId,
188
+ toolName: part.toolName,
189
+ result,
190
+ dynamic: !0
177
191
  }
178
192
  };
179
193
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/from-client.ts","../src/utils.ts","../src/sanity-agent-language-model.ts","../src/sanity-agent-provider.ts","../src/studio.ts"],"sourcesContent":["import type { Application } from './types'\n\n/**\n * Narrow client interface — only needs `config()`.\n * Used by {@link resolveApplication} which doesn't need URL generation.\n */\nexport type SanityClientConfig = {\n config(): { projectId?: string; dataset?: string; apiHost?: string }\n}\n\n/**\n * Client interface with URL generation support.\n * Any `@sanity/client` instance satisfies this.\n */\nexport type SanityClientLike = SanityClientConfig & {\n getUrl(uri: string): string\n}\n\n/** Extracts the project-scoped API origin from a Sanity client. */\nexport function getBaseUrl(client: SanityClientLike): string {\n return new URL(client.getUrl('/')).origin\n}\n\nexport type ClientDerivedConfig = {\n baseURL: string\n application: Application\n}\n\n/**\n * Derives Content Agent configuration from a Sanity client and workspace name.\n * Builds a CORS-compatible `baseURL` and an `application` targeting the workspace.\n *\n * @param client - A Sanity client instance. Any `@sanity/client` instance works.\n * @param workspace - The active workspace name (e.g. `'default'`).\n */\nexport function fromClient(client: SanityClientLike, workspace: string): ClientDerivedConfig {\n const { projectId, dataset } = client.config()\n\n if (!projectId) {\n throw new Error('fromClient: client is missing `projectId` in its config')\n }\n if (!dataset) {\n throw new Error('fromClient: client is missing `dataset` in its config')\n }\n\n return {\n baseURL: getBaseUrl(client),\n application: {\n name: workspace,\n resource: { id: `${projectId}.${dataset}`, type: 'dataset' },\n },\n }\n}\n","import type {\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n SharedV3Warning,\n} from '@ai-sdk/provider'\nimport { ERROR_CODES, type HeadlessErrorResponse } from '@sanity/agent-types'\nimport { isString, pickBy } from 'es-toolkit'\n\n/**\n * Builds headers for API requests, filtering out undefined values.\n *\n * @category Utilities\n */\nexport function buildHeaders(\n configHeaders: Record<string, string | undefined>,\n additionalHeaders?: Record<string, string>,\n): Record<string, string> {\n return pickBy(\n {\n 'Content-Type': 'application/json',\n ...additionalHeaders,\n ...configHeaders,\n },\n isString,\n )\n}\n\n/**\n * Constructs the full API URL for an endpoint.\n *\n * @category Utilities\n */\nexport function buildApiUrl(baseURL: string, apiVersion: string, endpoint: string): string {\n return `${baseURL}/${apiVersion}/agent${endpoint}`\n}\n\n/**\n * Custom error class for Sanity Agent API errors.\n * The {@link body} is parsed from the JSON response and follows the\n * {@link HeadlessErrorResponse} shape defined in `@sanity/agent-types`.\n *\n * @category Utilities\n */\nexport class SanityAgentError extends Error {\n readonly status: number\n readonly isRetryable: boolean\n readonly body: Partial<HeadlessErrorResponse>\n\n constructor(status: number, message: string) {\n super(`Sanity Agent API error: ${status} ${message}`)\n this.name = 'SanityAgentError'\n this.status = status\n this.isRetryable = status >= 500\n this.body = SanityAgentError.parseBody(message)\n }\n\n private static parseBody(message: string): Partial<HeadlessErrorResponse> {\n try {\n return JSON.parse(message)\n } catch {\n return {}\n }\n }\n}\n\n/**\n * Error class for errors received via SSE stream events.\n * Unlike {@link SanityAgentError} (which is thrown for HTTP-level errors before\n * streaming starts), this wraps errors received mid-stream as SSE `error` events.\n *\n * The {@link code} carries the backend's error classification (e.g. `UPSTREAM_OVERLOADED`)\n * so clients can take targeted action without parsing the message string.\n *\n * @category Utilities\n */\nexport class SanityAgentStreamError extends Error {\n readonly code: string | undefined\n\n constructor(message: string, code?: string) {\n super(message)\n this.name = 'SanityAgentStreamError'\n this.code = code\n }\n}\n\nexport const GENERIC_ERROR_MESSAGE = 'Sorry, something went wrong. Please try again.'\n\nexport type AgentErrorType =\n | { type: 'no_applications' }\n | { type: 'application_not_found' }\n | { type: 'upstream_overloaded' }\n | { type: 'upstream_error' }\n | { type: 'usage_limit_exceeded' }\n | { type: 'streaming_error' }\n | { type: 'unknown' }\n\nfunction mapErrorCode(code: string | undefined): AgentErrorType | null {\n switch (code) {\n case ERROR_CODES.NO_COMPATIBLE_APPLICATIONS:\n return { type: 'no_applications' }\n case ERROR_CODES.APPLICATION_NOT_FOUND:\n return { type: 'application_not_found' }\n case ERROR_CODES.UPSTREAM_OVERLOADED:\n return { type: 'upstream_overloaded' }\n case ERROR_CODES.UPSTREAM_ERROR:\n return { type: 'upstream_error' }\n case ERROR_CODES.AI_USAGE_LIMIT_EXCEEDED:\n return { type: 'usage_limit_exceeded' }\n case ERROR_CODES.STREAMING_ERROR:\n return { type: 'streaming_error' }\n default:\n return null\n }\n}\n\n/**\n * Classifies a caught error from the Content Agent API into a known error type.\n * Useful for clients that need to take different actions per error (e.g. show\n * resource-selection buttons). For the user-facing message, use {@link getAgentErrorMessage}.\n *\n * @category Utilities\n */\nexport function classifyAgentError(error: unknown): AgentErrorType {\n // Handle SSE stream errors (from parseStreamLine)\n if (error instanceof SanityAgentStreamError && error.code) {\n return mapErrorCode(error.code) ?? { type: 'unknown' }\n }\n\n // Handle HTTP-level errors (from fetchApi)\n if (error instanceof SanityAgentError) {\n if (error.status !== 400) {\n return { type: 'unknown' }\n }\n return mapErrorCode(error.body.code) ?? { type: 'unknown' }\n }\n\n return { type: 'unknown' }\n}\n\nexport function isAppRelatedError(classified: AgentErrorType): boolean {\n return classified.type === 'no_applications' || classified.type === 'application_not_found'\n}\n\nexport type AgentErrorMessageContext = {\n orgName?: string | null\n}\n\n/**\n * Resolves a user-facing error message for a classified agent error.\n * For errors that benefit from additional context (e.g. org name), the optional\n * {@link resolveContext} callback is invoked lazily — only when the error type\n * needs it. Non-contextual errors return the backend message as-is.\n *\n * @category Utilities\n */\nexport async function resolveAgentErrorMessage(\n classified: AgentErrorType,\n error: unknown,\n resolveContext?: () => Promise<AgentErrorMessageContext>,\n): Promise<string> {\n const fallback = getAgentErrorMessage(error)\n\n if (!resolveContext || !isAppRelatedError(classified)) return fallback\n\n const ctx = await resolveContext()\n if (!ctx.orgName) return fallback\n\n switch (classified.type) {\n case 'no_applications':\n return `Studio action required: To use the Content Agent in \"${ctx.orgName}\", you must have at least one Sanity Studio running version 5.1.0 or above. After upgrading, open your studio to connect it to the agent.`\n case 'application_not_found':\n return `The requested studio is not available in \"${ctx.orgName}\". You may not have access, or it may belong to a different organization.`\n default:\n return fallback\n }\n}\n\n/**\n * Extracts a user-facing error message from an agent API error.\n * Returns the message sent by the API when available, otherwise a generic fallback.\n *\n * @category Utilities\n */\nexport function getAgentErrorMessage(error: unknown): string {\n if (error instanceof SanityAgentStreamError) {\n return error.message\n }\n if (error instanceof SanityAgentError && error.body.message) {\n return error.body.message\n }\n return GENERIC_ERROR_MESSAGE\n}\n\nexport interface FetchOptions {\n url: string\n body: unknown\n headers: Record<string, string>\n credentials?: RequestCredentials\n signal?: AbortSignal\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Makes a POST request and returns the response.\n * Throws SanityAgentError if the response is not ok.\n *\n * @category Utilities\n */\nexport async function fetchApi(options: FetchOptions): Promise<Response> {\n const fetchFn = options.fetch ?? globalThis.fetch\n\n const response = await fetchFn(options.url, {\n method: 'POST',\n headers: options.headers,\n body: JSON.stringify(options.body),\n signal: options.signal,\n ...(options.credentials && { credentials: options.credentials }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SanityAgentError(response.status, errorText)\n }\n\n return response\n}\n\n/**\n * Extracts text content from message parts (user or assistant).\n *\n * @category Utilities\n */\nexport function extractTextFromParts(\n content: string | Array<{ type: string; text?: string }>,\n): string {\n if (typeof content === 'string') {\n return content\n }\n\n return content\n .filter(\n (part): part is { type: 'text'; text: string } =>\n part.type === 'text' && typeof part.text === 'string',\n )\n .map((part) => part.text)\n .join('\\n')\n}\n\n/**\n * Creates an empty V3 usage object. Our API does not return token counts.\n *\n * @category Utilities\n */\nexport function createEmptyUsage(): LanguageModelV3Usage {\n return {\n inputTokens: {\n total: 0,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: {\n total: 0,\n text: undefined,\n reasoning: undefined,\n },\n }\n}\n\n/**\n * Maps API finish reasons to LanguageModelV3FinishReason.\n *\n * @category Utilities\n */\nexport function mapFinishReason(reason: string | undefined): LanguageModelV3FinishReason {\n const unified = (() => {\n switch (reason) {\n case 'stop':\n case 'length':\n case 'content-filter':\n case 'tool-calls':\n case 'error':\n return reason\n default:\n return 'other' as const\n }\n })()\n\n return { unified, raw: reason }\n}\n\nexport type ParsedStreamLine =\n | { kind: 'part'; part: LanguageModelV3StreamPart }\n | { kind: 'finish'; finishReason: LanguageModelV3FinishReason }\n | { kind: 'data'; type: string; data: unknown }\n\n/**\n * Parses a single SSE line from the AI SDK v6 UI Message Stream protocol.\n * Returns null if the line cannot be parsed or should be ignored.\n *\n * Format: `data: <json>\\n`\n *\n * Supported types:\n * text-start – Text block start (forwarded as-is)\n * text-delta – Text chunk\n * text-end – Text block end (forwarded as-is)\n * tool-input-available – Tool call (client tools only, without providerExecuted)\n * tool-output-available – Tool result (informational, skipped)\n * finish – Finish event with reason\n * error – Error event\n * start – Stream start (skipped)\n * [DONE] – Stream termination (skipped)\n *\n * @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol\n *\n * @param line A single line from the SSE byte stream.\n * @param textContentId Fallback ID for text parts that lack an explicit id.\n *\n * @category Utilities\n */\nexport function parseStreamLine(line: string, textContentId: string): ParsedStreamLine | null {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data: ')) return null\n\n const payload = trimmed.slice(6)\n if (payload === '[DONE]') return null\n\n let part: Record<string, unknown>\n try {\n part = JSON.parse(payload) as Record<string, unknown>\n } catch {\n return null\n }\n\n const type = part.type as string | undefined\n if (!type) return null\n\n switch (type) {\n case 'text-start': {\n const id = (part.id as string) || textContentId\n return { kind: 'part', part: { type: 'text-start', id } }\n }\n\n case 'text-delta': {\n const id = (part.id as string) || textContentId\n const delta = part.delta as string\n if (typeof delta !== 'string') return null\n return { kind: 'part', part: { type: 'text-delta', id, delta } }\n }\n\n case 'text-end': {\n const id = (part.id as string) || textContentId\n return { kind: 'part', part: { type: 'text-end', id } }\n }\n\n case 'tool-input-available': {\n // Skip server-executed tools — only forward client tools for local execution.\n if (part.providerExecuted) return null\n\n const input = part.input\n return {\n kind: 'part',\n part: {\n type: 'tool-call',\n toolCallId: part.toolCallId as string,\n toolName: part.toolName as string,\n input: typeof input === 'string' ? input : JSON.stringify(input),\n },\n }\n }\n\n case 'error': {\n const errorText = (part.errorText as string) ?? 'Unknown error'\n const errorCode = part.errorCode as string | undefined\n return {\n kind: 'part',\n part: {\n type: 'error',\n error: new SanityAgentStreamError(errorText, errorCode),\n },\n }\n }\n\n case 'abort': {\n return { kind: 'data', type: 'abort', data: { reason: part.reason } }\n }\n\n case 'finish': {\n return {\n kind: 'finish',\n finishReason: mapFinishReason(part.finishReason as string | undefined),\n }\n }\n\n default: {\n if (type.startsWith('data-')) {\n return { kind: 'data', type: type.slice(5), data: part.data }\n }\n return null\n }\n }\n}\n\n/**\n * Extracts accumulated text from a streaming response body.\n * Used for simple streaming cases where only the final text is needed.\n *\n * @category Utilities\n */\nexport async function extractTextFromStream(\n responseBody: ReadableStream<Uint8Array>,\n): Promise<string> {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n const textContentId = 'extract'\n let text = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n for (const line of chunk.split('\\n')) {\n const parsed = parseStreamLine(line, textContentId)\n if (parsed?.kind === 'part' && parsed.part.type === 'text-delta') {\n text += parsed.part.delta\n }\n }\n }\n\n return text\n}\n\n/**\n * Creates a ReadableStream that parses AI SDK v6 UI Message Stream SSE data\n * from a response body and emits LanguageModelV3 stream parts.\n *\n * Text lifecycle events (text-start / text-end) are forwarded from the server.\n * Tool calls are tracked to ensure the finish reason is set correctly.\n *\n * @category Utilities\n */\nexport function createSSEStream(\n responseBody: ReadableStream<Uint8Array>,\n warnings: SharedV3Warning[],\n onData?: (type: string, data: unknown) => void,\n): ReadableStream<LanguageModelV3StreamPart> {\n let finishReason: LanguageModelV3FinishReason = { unified: 'other', raw: undefined }\n const fallbackTextId = crypto.randomUUID()\n let hasToolCalls = false\n\n return new ReadableStream<LanguageModelV3StreamPart>({\n async start(controller) {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n controller.enqueue({ type: 'stream-start', warnings })\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n const parsed = parseStreamLine(line, fallbackTextId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n }\n controller.enqueue(parsed.part)\n } else if (parsed?.kind === 'finish') {\n finishReason = parsed.finishReason\n } else if (parsed?.kind === 'data') {\n onData?.(parsed.type, parsed.data)\n }\n }\n }\n\n if (buffer.trim()) {\n const parsed = parseStreamLine(buffer, fallbackTextId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n }\n controller.enqueue(parsed.part)\n } else if (parsed?.kind === 'finish') {\n finishReason = parsed.finishReason\n } else if (parsed?.kind === 'data') {\n onData?.(parsed.type, parsed.data)\n }\n }\n\n // If tool calls were emitted, override finish reason so streamText\n // knows to execute tools and continue with the next step.\n if (hasToolCalls && finishReason.unified !== 'tool-calls') {\n finishReason = { unified: 'tool-calls', raw: finishReason.raw ?? 'tool-calls' }\n }\n\n controller.enqueue({\n type: 'finish',\n finishReason,\n usage: createEmptyUsage(),\n })\n } catch (error) {\n controller.enqueue({\n type: 'error',\n error: error instanceof Error ? error : new Error(String(error)),\n })\n } finally {\n controller.close()\n }\n },\n })\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3CallOptions,\n LanguageModelV3Content,\n LanguageModelV3GenerateResult,\n LanguageModelV3Prompt,\n LanguageModelV3StreamResult,\n SharedV3Warning,\n} from '@ai-sdk/provider'\nimport type { ClientToolDefinition } from '@sanity/agent-types'\nimport { groupBy } from 'es-toolkit'\nimport type { AgentSettings, Message } from './types'\nimport {\n buildApiUrl,\n buildHeaders,\n createEmptyUsage,\n createSSEStream,\n extractTextFromParts,\n fetchApi,\n parseStreamLine,\n} from './utils'\n\n/**\n * @category Model\n */\nexport interface ContentAgentModelConfig {\n provider: string\n baseURL: string\n apiVersion: string\n organizationId: string\n headers: () => Record<string, string | undefined>\n credentials?: RequestCredentials\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Extended message type for the wire format that supports tool calls and tool results\n * in addition to plain text messages.\n */\ntype WireMessage =\n | Message\n | {\n role: 'assistant'\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'tool-call'; toolCallId: string; toolName: string; input: string }\n >\n }\n | {\n role: 'tool'\n content: Array<{\n type: 'tool-result'\n toolCallId: string\n toolName: string\n output: unknown\n }>\n }\n\nconst UNSUPPORTED_SETTINGS = [\n 'maxOutputTokens',\n 'temperature',\n 'topP',\n 'topK',\n 'frequencyPenalty',\n 'presencePenalty',\n 'seed',\n] as const\n\n/**\n * Vercel AI SDK `LanguageModelV1` implementation that communicates with the\n * Sanity Content Agent API via a conversation thread.\n *\n * Typically you don't construct this directly — use\n * {@link createContentAgent | createContentAgent().agent(threadId)} instead.\n *\n * @category Model\n *\n * @example\n * ```ts\n * import { createContentAgent } from 'content-agent'\n * import { streamText } from 'ai'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n */\nexport class ContentAgentModel implements LanguageModelV3 {\n readonly specificationVersion = 'v3' as const\n readonly provider: string\n readonly modelId: string\n readonly supportedUrls: Record<string, RegExp[]> = {}\n\n private readonly config: ContentAgentModelConfig\n private readonly threadId: string\n private readonly settings: AgentSettings\n\n constructor(\n threadId: string,\n settings: AgentSettings | undefined,\n config: ContentAgentModelConfig,\n ) {\n this.threadId = threadId\n this.modelId = `content-agent:${threadId}`\n this.provider = config.provider\n this.settings = settings ?? {}\n this.config = config\n }\n\n /**\n * Converts a LanguageModelV3Prompt into wire messages for the agent API.\n *\n * Handles all message roles:\n * - system: plain text system messages\n * - user: extracts text from content parts\n * - assistant: extracts text and/or tool-call parts\n * - tool: converts tool-result parts for the API\n */\n private convertPromptToMessages(prompt: LanguageModelV3Prompt): WireMessage[] {\n const messages: WireMessage[] = []\n\n for (const message of prompt) {\n if (message.role === 'system') {\n if (message.content) {\n messages.push({ role: 'system', content: message.content })\n }\n continue\n }\n\n if (message.role === 'tool') {\n const toolResults = message.content\n .filter((part) => part.type === 'tool-result')\n .map((part) => {\n const p = part as {\n toolCallId: string\n toolName: string\n output: { type: string; value: unknown } | unknown\n }\n return {\n type: 'tool-result' as const,\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n output:\n p.output && typeof p.output === 'object' && 'value' in p.output\n ? (p.output as { value: unknown }).value\n : p.output,\n }\n })\n\n if (toolResults.length > 0) {\n messages.push({ role: 'tool', content: toolResults })\n }\n continue\n }\n\n if (message.role === 'assistant') {\n const content = message.content as Array<{\n type: string\n text?: string\n toolCallId?: string\n toolName?: string\n input?: string\n }>\n\n const grouped = groupBy(content, (p) => p.type)\n const toolCalls = grouped['tool-call'] ?? []\n const textParts = grouped.text ?? []\n\n if (toolCalls.length > 0) {\n const parts = [\n ...textParts\n .filter((p) => p.text)\n .map((p) => ({ type: 'text' as const, text: p.text! })),\n ...toolCalls.map((tc) => ({\n type: 'tool-call' as const,\n toolCallId: tc.toolCallId!,\n toolName: tc.toolName!,\n input: tc.input!,\n })),\n ]\n\n if (parts.length > 0) {\n messages.push({ role: 'assistant', content: parts })\n }\n } else {\n const text = extractTextFromParts(content as Array<{ type: string; text?: string }>)\n if (text) {\n messages.push({ role: 'assistant', content: text })\n }\n }\n continue\n }\n\n // user messages\n const text = extractTextFromParts(message.content as Array<{ type: string; text?: string }>)\n if (text) {\n messages.push({ role: message.role, content: text })\n }\n }\n\n return messages\n }\n\n /**\n * Extracts client tool definitions from the model call options.\n * Only the schemas are sent — execute functions stay on the client.\n */\n private extractClientTools(\n options: LanguageModelV3CallOptions,\n ): ClientToolDefinition[] | undefined {\n if (!options.tools?.length) return undefined\n\n const clientTools = options.tools\n .filter((t) => t.type === 'function')\n .map((t) => ({\n type: 'function' as const,\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as Record<string, unknown>,\n }))\n\n return clientTools.length > 0 ? clientTools : undefined\n }\n\n private collectWarnings(options: LanguageModelV3CallOptions): SharedV3Warning[] {\n const warnings: SharedV3Warning[] = []\n\n for (const setting of UNSUPPORTED_SETTINGS) {\n if (options[setting] !== undefined) {\n warnings.push({ type: 'unsupported', feature: setting })\n }\n }\n\n if (options.stopSequences?.length) {\n warnings.push({ type: 'unsupported', feature: 'stopSequences' })\n }\n\n if (options.responseFormat && options.responseFormat.type !== 'text') {\n warnings.push({ type: 'unsupported', feature: 'responseFormat' })\n }\n\n return warnings\n }\n\n private buildRequestBody(options: LanguageModelV3CallOptions, stream: boolean) {\n const clientTools = this.extractClientTools(options)\n const toolChoice = options.toolChoice\n\n return {\n messages: this.convertPromptToMessages(options.prompt),\n stream,\n ...(clientTools && { clientTools }),\n ...(toolChoice && { toolChoice }),\n ...(this.settings.application && { application: this.settings.application }),\n ...(this.settings.config && { config: this.settings.config }),\n ...(this.settings.format && { format: this.settings.format }),\n ...(this.settings.clientId && { clientId: this.settings.clientId }),\n }\n }\n\n private getResponseHeaders(response: Response): Record<string, string> {\n return Object.fromEntries(response.headers.entries())\n }\n\n async doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult> {\n // Always request a stream from the backend and buffer the result.\n // This keeps the HTTP connection alive with incremental data, avoiding\n // 504 gateway timeouts on long-running agent requests.\n const body = this.buildRequestBody(options, true)\n const warnings = this.collectWarnings(options)\n\n const response = await fetchApi({\n url: buildApiUrl(\n this.config.baseURL,\n this.config.apiVersion,\n `/${this.config.organizationId}/threads/${this.threadId}`,\n ),\n body,\n headers: buildHeaders(this.config.headers()),\n credentials: this.config.credentials,\n signal: options.abortSignal,\n fetch: this.config.fetch,\n })\n\n if (!response.body) {\n throw new Error('No response body for generate request')\n }\n\n const { content, finishReason } = await this.extractContentFromStream(response.body)\n\n return {\n content,\n finishReason,\n usage: createEmptyUsage(),\n request: { body },\n response: { headers: this.getResponseHeaders(response) },\n warnings,\n }\n }\n\n /**\n * Parses a streamed response body into content parts (text and tool calls)\n * and a finish reason. Used by doGenerate to buffer the full response.\n */\n private async extractContentFromStream(responseBody: ReadableStream<Uint8Array>): Promise<{\n content: LanguageModelV3Content[]\n finishReason: LanguageModelV3GenerateResult['finishReason']\n }> {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n const textContentId = 'generate'\n let text = ''\n let hasToolCalls = false\n const content: LanguageModelV3Content[] = []\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n for (const line of chunk.split('\\n')) {\n const parsed = parseStreamLine(line, textContentId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'text-delta') {\n text += parsed.part.delta\n } else if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n const tc = parsed.part as { toolCallId: string; toolName: string; input: string }\n content.push({\n type: 'tool-call',\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n input: tc.input,\n })\n }\n }\n }\n }\n\n if (text) {\n content.unshift({ type: 'text', text })\n }\n\n const finishReason = hasToolCalls\n ? { unified: 'tool-calls' as const, raw: 'tool-calls' }\n : { unified: 'stop' as const, raw: undefined }\n\n return { content, finishReason }\n }\n\n async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {\n const body = this.buildRequestBody(options, true)\n const warnings = this.collectWarnings(options)\n\n const response = await fetchApi({\n url: buildApiUrl(\n this.config.baseURL,\n this.config.apiVersion,\n `/${this.config.organizationId}/threads/${this.threadId}`,\n ),\n body,\n headers: buildHeaders(this.config.headers()),\n credentials: this.config.credentials,\n signal: options.abortSignal,\n fetch: this.config.fetch,\n })\n\n if (!response.body) {\n throw new Error('No response body for streaming request')\n }\n\n return {\n stream: createSSEStream(response.body, warnings, this.settings.onStreamData),\n request: { body },\n response: { headers: this.getResponseHeaders(response) },\n }\n }\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider'\nimport type { SanityClientConfig } from './from-client'\nimport { ContentAgentModel, type ContentAgentModelConfig } from './sanity-agent-language-model'\nimport type {\n AgentSettings,\n Application,\n ContentAgentProviderSettings,\n GenerateOptions,\n GenerateResult,\n GenerateSettings,\n} from './types'\nimport {\n buildApiUrl,\n buildHeaders,\n extractTextFromStream,\n fetchApi,\n SanityAgentError,\n} from './utils'\n\nconst DEFAULT_BASE_URL = 'https://api.sanity.io'\nconst DEFAULT_API_VERSION = 'v2026-01-01'\n\n/**\n * A Content Agent provider instance returned by {@link createContentAgent}.\n *\n * Provides two modes of interaction:\n * - **Conversation threads** via {@link ContentAgentProvider.agent | .agent()} — stateful, multi-turn conversations with server-side message history.\n * - **One-shot prompts** via {@link ContentAgentProvider.prompt | .prompt()} — stateless, single-turn interactions.\n *\n * @category Provider\n */\nexport interface ContentAgentProvider {\n /**\n * Creates a Vercel AI SDK {@link https://sdk.vercel.ai/docs/ai-sdk-core | LanguageModelV1} for a conversation thread.\n *\n * The thread ID identifies the conversation and maintains message history server-side.\n * If a thread with that ID already exists, the conversation continues. If not, a new\n * thread is automatically created.\n *\n * Use the returned model with `generateText` or `streamText` from the `ai` package.\n *\n * @example\n * ```ts\n * import { generateText, streamText } from 'ai'\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * // Non-streaming\n * const { text } = await generateText({ model, prompt: 'What blog posts do I have?' })\n *\n * // Streaming\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n *\n * @param threadId - Unique identifier for the conversation thread.\n * @param settings - Optional agent settings (application, capabilities, filters, etc.).\n */\n agent(threadId: string, settings?: AgentSettings): LanguageModelV3\n\n /**\n * Lists available applications for the organization.\n *\n * Returns all Sanity Studio applications the authenticated user has access to.\n * Use the `key` from a returned application to target it when creating an agent.\n *\n * @example\n * ```ts\n * const apps = await contentAgent.applications()\n * const app = apps.find((app) => app.title === \"My Studio\")\n *\n * const model = contentAgent.agent(\"my-thread\", {\n * application: { key: app.key },\n * })\n * ```\n */\n applications(): Promise<Application[]>\n\n /**\n * Matches a Sanity client's `projectId` and `dataset` against the org's application list.\n * Throws if no match is found. Prefer {@link createStudioAgent} which calls this automatically.\n */\n resolveApplication(client: SanityClientConfig, workspace: string): Promise<Application>\n\n /**\n * Sends a one-shot prompt to the Content Agent (stateless).\n *\n * Unlike {@link ContentAgentProvider.agent | .agent()}, this does not create or use a conversation thread.\n * Each call is independent with no message history or persistence.\n * Ideal for simple, single-turn interactions.\n *\n * @example\n * ```ts\n * const { text } = await contentAgent.prompt(\n * {\n * application: { key: 'projectId.datasetName' },\n * config: { capabilities: { read: true, write: false } },\n * instructions: 'Be concise',\n * },\n * { message: 'List my 5 most recent posts' },\n * )\n * ```\n *\n * @param settings - Generation settings (application, capabilities, instructions, etc.).\n * @param options - The prompt message and request options.\n */\n prompt(settings: GenerateSettings, options: GenerateOptions): Promise<GenerateResult>\n}\n\n/**\n * Creates a Content Agent provider instance.\n *\n * The provider is the main entry point for interacting with the Sanity Content Agent API.\n * It returns an object with methods for conversation threads and one-shot prompts.\n *\n * @category Provider\n *\n * @example Basic setup\n * ```ts\n * import { createContentAgent } from 'content-agent'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n * ```\n *\n * @example Using with Vercel AI SDK for conversation threads\n * ```ts\n * import { createContentAgent } from 'content-agent'\n * import { generateText, streamText } from 'ai'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * // Non-streaming\n * const { text } = await generateText({ model, prompt: 'What blog posts do I have?' })\n *\n * // Streaming\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n *\n * @example One-shot generation (stateless, no thread)\n * ```ts\n * const { text } = await contentAgent.prompt(\n * {\n * application: { key: 'projectId.datasetName' },\n * config: { capabilities: { read: true, write: false } },\n * instructions: 'Be concise',\n * },\n * { message: 'List my 5 most recent posts' },\n * )\n * ```\n *\n * @param options - Provider configuration including organization ID, token, and optional overrides.\n */\nexport function createContentAgent(options: ContentAgentProviderSettings): ContentAgentProvider {\n const baseURL = options.baseURL?.replace(/\\/$/, '') ?? DEFAULT_BASE_URL\n const apiVersion = options.apiVersion ?? DEFAULT_API_VERSION\n const { organizationId } = options\n\n const getHeaders = (): Record<string, string | undefined> => ({\n ...(options.token && { Authorization: `Bearer ${options.token}` }),\n ...options.headers,\n })\n\n // When no token is provided, include credentials so the browser sends\n // cookies automatically — this is the Studio / browser auth path.\n const credentials: RequestCredentials | undefined = options.token ? undefined : 'include'\n\n const config: ContentAgentModelConfig = {\n provider: 'content-agent',\n baseURL,\n apiVersion,\n organizationId,\n headers: getHeaders,\n credentials,\n fetch: options.fetch,\n }\n\n const agent = (threadId: string, settings?: AgentSettings): LanguageModelV3 => {\n return new ContentAgentModel(\n threadId,\n {\n ...settings,\n application: settings?.application ?? options.application,\n },\n config,\n )\n }\n\n let applicationsPromise: Promise<Application[]> | null = null\n\n const applications = (): Promise<Application[]> => {\n if (!applicationsPromise) {\n applicationsPromise = (async () => {\n const fetchFn = options.fetch ?? globalThis.fetch\n const url = buildApiUrl(baseURL, apiVersion, `/${organizationId}/applications`)\n const headers = buildHeaders(getHeaders())\n\n const response = await fetchFn(url, {\n method: 'GET',\n headers,\n ...(credentials && { credentials }),\n })\n\n if (!response.ok) {\n applicationsPromise = null\n const errorText = await response.text()\n throw new SanityAgentError(response.status, errorText)\n }\n\n const json = (await response.json()) as { applications?: Application[] }\n return json.applications ?? []\n })()\n }\n\n return applicationsPromise\n }\n\n const prompt = async (\n settings: GenerateSettings,\n generateOptions: GenerateOptions,\n ): Promise<GenerateResult> => {\n const effectiveApplication = settings.application ?? options.application\n\n const body = {\n organizationId,\n message: generateOptions.message,\n stream: generateOptions.stream ?? false,\n ...(effectiveApplication && { application: effectiveApplication }),\n ...(settings.config && { config: settings.config }),\n ...(settings.instructions && { instructions: settings.instructions }),\n ...(settings.format && { format: settings.format }),\n }\n\n const response = await fetchApi({\n url: buildApiUrl(baseURL, apiVersion, '/prompt'),\n body,\n headers: buildHeaders(getHeaders()),\n credentials,\n signal: generateOptions.abortSignal,\n fetch: options.fetch,\n })\n\n if (body.stream) {\n if (!response.body) {\n throw new SanityAgentError(500, 'No response body')\n }\n return { text: await extractTextFromStream(response.body) }\n }\n\n const json = (await response.json()) as { text?: string }\n return { text: json.text ?? '' }\n }\n\n const resolveApplication = async (\n client: SanityClientConfig,\n workspace: string,\n ): Promise<Application> => {\n const { projectId, dataset } = client.config()\n if (!projectId || !dataset) {\n throw new Error(\n 'resolveApplication: client is missing `projectId` or `dataset` in its config',\n )\n }\n\n const resourceId = `${projectId}.${dataset}`\n const apps = await applications()\n const match = apps.find((app) => app.resource?.id === resourceId && app.name === workspace)\n\n if (!match) {\n throw new SanityAgentError(\n 400,\n JSON.stringify({\n code: 'APPLICATION_NOT_FOUND',\n message: `No application found for resource \"${resourceId}\" with workspace \"${workspace}\". Make sure the Studio has been deployed and belongs to this organization.`,\n }),\n )\n }\n\n return match\n }\n\n return { agent, applications, resolveApplication, prompt }\n}\n","import type { LanguageModelV3, LanguageModelV3CallOptions } from '@ai-sdk/provider'\nimport { getBaseUrl, type SanityClientConfig, type SanityClientLike } from './from-client'\nimport type { ContentAgentProvider } from './sanity-agent-provider'\nimport { createContentAgent } from './sanity-agent-provider'\nimport type { AgentSettings, ContentAgentProviderSettings } from './types'\nimport { buildHeaders, SanityAgentError } from './utils'\n\nconst PROJECTS_API_VERSION = 'v2024-01-01'\n\n/**\n * Module-level cache for setup promises. Keyed by `projectId.dataset.workspace`,\n * so repeated calls to `createStudioAgent` with the same params (e.g. React strict\n * mode double-render, component remounts) reuse the same in-flight or resolved setup.\n */\nconst providerCache = new Map<string, Promise<ContentAgentProvider>>()\n\ntype ProjectResponse = {\n id: string\n organizationId?: string | null\n}\n\n/**\n * Options for {@link createStudioAgent}.\n */\nexport type StudioAgentOptions = {\n /** Custom fetch implementation. Useful for testing. */\n fetch?: typeof globalThis.fetch\n\n /**\n * Additional provider settings forwarded to {@link createContentAgent}.\n * `organizationId`, `baseURL`, and `application` are derived automatically.\n */\n providerOptions?: Omit<ContentAgentProviderSettings, 'organizationId' | 'baseURL' | 'application'>\n}\n\nasync function fetchOrganizationId(\n baseURL: string,\n projectId: string,\n fetchFn: typeof globalThis.fetch,\n): Promise<string> {\n const url = `${baseURL}/${PROJECTS_API_VERSION}/projects/${projectId}`\n\n const response = await fetchFn(url, {\n method: 'GET',\n headers: buildHeaders({}),\n credentials: 'include',\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SanityAgentError(\n response.status,\n JSON.stringify({\n code: 'PROJECT_FETCH_FAILED',\n message: `Failed to fetch project \"${projectId}\": ${errorText}`,\n }),\n )\n }\n\n const project = (await response.json()) as ProjectResponse\n\n if (!project.organizationId) {\n throw new SanityAgentError(\n 400,\n JSON.stringify({\n code: 'NO_ORGANIZATION',\n message: `Project \"${projectId}\" is not assigned to an organization. Content Agent requires an organization-level project.`,\n }),\n )\n }\n\n return project.organizationId\n}\n\n/**\n * Resolves the full provider by fetching org ID and application. The returned\n * promise is cached so the setup only runs once.\n */\nfunction initProvider(\n projectId: string,\n dataset: string,\n workspace: string,\n baseURL: string,\n fetchFn: typeof globalThis.fetch,\n providerOptions?: StudioAgentOptions['providerOptions'],\n): Promise<ContentAgentProvider> {\n return fetchOrganizationId(baseURL, projectId, fetchFn).then(async (organizationId) => {\n const tempProvider = createContentAgent({\n organizationId,\n baseURL,\n fetch: fetchFn,\n })\n\n const client: SanityClientConfig = { config: () => ({ projectId, dataset }) }\n const application = await tempProvider.resolveApplication(client, workspace)\n\n return createContentAgent({\n ...providerOptions,\n organizationId,\n baseURL,\n application,\n })\n })\n}\n\n/**\n * A {@link LanguageModelV3} that defers to a lazily-resolved provider.\n * The first call to `doGenerate` or `doStream` awaits the setup promise,\n * then delegates to the real model.\n */\nclass LazyContentAgentModel implements LanguageModelV3 {\n readonly specificationVersion = 'v3' as const\n readonly provider = 'content-agent'\n readonly modelId: string\n readonly supportedUrls: Record<string, RegExp[]> = {}\n\n private readonly providerPromise: Promise<ContentAgentProvider>\n private readonly threadId: string\n private readonly settings: AgentSettings | undefined\n private resolvedModel: LanguageModelV3 | null = null\n\n constructor(\n threadId: string,\n settings: AgentSettings | undefined,\n providerPromise: Promise<ContentAgentProvider>,\n ) {\n this.threadId = threadId\n this.settings = settings\n this.modelId = `content-agent:${threadId}`\n this.providerPromise = providerPromise\n }\n\n private async getModel(): Promise<LanguageModelV3> {\n if (!this.resolvedModel) {\n const provider = await this.providerPromise\n this.resolvedModel = provider.agent(this.threadId, this.settings)\n }\n return this.resolvedModel\n }\n\n async doGenerate(options: LanguageModelV3CallOptions) {\n const model = await this.getModel()\n return model.doGenerate(options)\n }\n\n async doStream(options: LanguageModelV3CallOptions) {\n const model = await this.getModel()\n return model.doStream(options)\n }\n}\n\n/**\n * Creates a Content Agent provider fully configured for a Studio context.\n *\n * The call is **synchronous** — safe for React components and `useMemo`.\n * Async setup (org ID + application resolution) is deferred to the first API call.\n *\n * @param client - A Sanity client instance. Available via `useClient()` in Studio.\n * @param workspace - The active workspace name. Available via `useWorkspace().name`.\n * @param options - Optional overrides (custom fetch, additional provider settings).\n */\nexport function createStudioAgent(\n client: SanityClientLike,\n workspace: string,\n options?: StudioAgentOptions,\n): ContentAgentProvider {\n const { projectId, dataset } = client.config()\n\n if (!projectId) {\n throw new Error('createStudioAgent: client is missing `projectId` in its config')\n }\n if (!dataset) {\n throw new Error('createStudioAgent: client is missing `dataset` in its config')\n }\n\n const fetchFn = options?.fetch ?? globalThis.fetch\n const baseURL = getBaseUrl(client)\n const cacheKey = `${projectId}.${dataset}.${workspace}`\n\n // Reuse an existing setup promise for the same project/dataset/workspace,\n // or start a new one. Custom fetch or providerOptions bypass the cache\n // since they indicate a non-default configuration (e.g. tests).\n const useCache = !options?.fetch && !options?.providerOptions\n\n let providerPromise: Promise<ContentAgentProvider>\n\n if (useCache && providerCache.has(cacheKey)) {\n providerPromise = providerCache.get(cacheKey)!\n } else {\n providerPromise = initProvider(\n projectId,\n dataset,\n workspace,\n baseURL,\n fetchFn,\n options?.providerOptions,\n )\n\n if (useCache) {\n providerCache.set(cacheKey, providerPromise)\n\n // Evict on failure so a transient error doesn't stick forever\n providerPromise.catch(() => {\n providerCache.delete(cacheKey)\n })\n }\n }\n\n const awaitProvider = async <T>(fn: (provider: ContentAgentProvider) => Promise<T>): Promise<T> =>\n fn(await providerPromise)\n\n return {\n agent(threadId: string, settings?: AgentSettings): LanguageModelV3 {\n return new LazyContentAgentModel(threadId, settings, providerPromise)\n },\n\n applications() {\n return awaitProvider((p) => p.applications())\n },\n\n resolveApplication(resolveClient: SanityClientConfig, resolveWorkspace: string) {\n return awaitProvider((p) => p.resolveApplication(resolveClient, resolveWorkspace))\n },\n\n prompt(settings, generateOptions) {\n return awaitProvider((p) => p.prompt(settings, generateOptions))\n },\n }\n}\n"],"names":["text"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/from-client.ts","../src/utils.ts","../src/sanity-agent-language-model.ts","../src/sanity-agent-provider.ts","../src/studio.ts"],"sourcesContent":["import type { Application } from './types'\n\n/**\n * Narrow client interface — only needs `config()`.\n * Used by {@link resolveApplication} which doesn't need URL generation.\n */\nexport type SanityClientConfig = {\n config(): { projectId?: string; dataset?: string; apiHost?: string }\n}\n\n/**\n * Client interface with URL generation support.\n * Any `@sanity/client` instance satisfies this.\n */\nexport type SanityClientLike = SanityClientConfig & {\n getUrl(uri: string): string\n}\n\n/** Extracts the project-scoped API origin from a Sanity client. */\nexport function getBaseUrl(client: SanityClientLike): string {\n return new URL(client.getUrl('/')).origin\n}\n\nexport type ClientDerivedConfig = {\n baseURL: string\n application: Application\n}\n\n/**\n * Derives Content Agent configuration from a Sanity client and workspace name.\n * Builds a CORS-compatible `baseURL` and an `application` targeting the workspace.\n *\n * @param client - A Sanity client instance. Any `@sanity/client` instance works.\n * @param workspace - The active workspace name (e.g. `'default'`).\n */\nexport function fromClient(client: SanityClientLike, workspace: string): ClientDerivedConfig {\n const { projectId, dataset } = client.config()\n\n if (!projectId) {\n throw new Error('fromClient: client is missing `projectId` in its config')\n }\n if (!dataset) {\n throw new Error('fromClient: client is missing `dataset` in its config')\n }\n\n return {\n baseURL: getBaseUrl(client),\n application: {\n name: workspace,\n resource: { id: `${projectId}.${dataset}`, type: 'dataset' },\n },\n }\n}\n","import type {\n LanguageModelV3FinishReason,\n LanguageModelV3StreamPart,\n LanguageModelV3Usage,\n SharedV3Warning,\n} from '@ai-sdk/provider'\nimport { ERROR_CODES, type HeadlessErrorResponse } from '@sanity/agent-types'\nimport { isString, pickBy } from 'es-toolkit'\n\n/**\n * Builds headers for API requests, filtering out undefined values.\n *\n * @category Utilities\n */\nexport function buildHeaders(\n configHeaders: Record<string, string | undefined>,\n additionalHeaders?: Record<string, string>,\n): Record<string, string> {\n return pickBy(\n {\n 'Content-Type': 'application/json',\n ...additionalHeaders,\n ...configHeaders,\n },\n isString,\n )\n}\n\n/**\n * Constructs the full API URL for an endpoint.\n *\n * @category Utilities\n */\nexport function buildApiUrl(baseURL: string, apiVersion: string, endpoint: string): string {\n return `${baseURL}/${apiVersion}/agent${endpoint}`\n}\n\n/**\n * Custom error class for Sanity Agent API errors.\n * The {@link body} is parsed from the JSON response and follows the\n * {@link HeadlessErrorResponse} shape defined in `@sanity/agent-types`.\n *\n * @category Utilities\n */\nexport class SanityAgentError extends Error {\n readonly status: number\n readonly isRetryable: boolean\n readonly body: Partial<HeadlessErrorResponse>\n\n constructor(status: number, message: string) {\n super(`Sanity Agent API error: ${status} ${message}`)\n this.name = 'SanityAgentError'\n this.status = status\n this.isRetryable = status >= 500\n this.body = SanityAgentError.parseBody(message)\n }\n\n private static parseBody(message: string): Partial<HeadlessErrorResponse> {\n try {\n return JSON.parse(message)\n } catch {\n return {}\n }\n }\n}\n\n/**\n * Error class for errors received via SSE stream events.\n * Unlike {@link SanityAgentError} (which is thrown for HTTP-level errors before\n * streaming starts), this wraps errors received mid-stream as SSE `error` events.\n *\n * The {@link code} carries the backend's error classification (e.g. `UPSTREAM_OVERLOADED`)\n * so clients can take targeted action without parsing the message string.\n *\n * @category Utilities\n */\nexport class SanityAgentStreamError extends Error {\n readonly code: string | undefined\n\n constructor(message: string, code?: string) {\n super(message)\n this.name = 'SanityAgentStreamError'\n this.code = code\n }\n}\n\nexport const GENERIC_ERROR_MESSAGE = 'Sorry, something went wrong. Please try again.'\n\nexport type AgentErrorType =\n | { type: 'no_applications' }\n | { type: 'application_not_found' }\n | { type: 'upstream_overloaded' }\n | { type: 'upstream_error' }\n | { type: 'usage_limit_exceeded' }\n | { type: 'streaming_error' }\n | { type: 'unknown' }\n\nfunction mapErrorCode(code: string | undefined): AgentErrorType | null {\n switch (code) {\n case ERROR_CODES.NO_COMPATIBLE_APPLICATIONS:\n return { type: 'no_applications' }\n case ERROR_CODES.APPLICATION_NOT_FOUND:\n return { type: 'application_not_found' }\n case ERROR_CODES.UPSTREAM_OVERLOADED:\n return { type: 'upstream_overloaded' }\n case ERROR_CODES.UPSTREAM_ERROR:\n return { type: 'upstream_error' }\n case ERROR_CODES.AI_USAGE_LIMIT_EXCEEDED:\n return { type: 'usage_limit_exceeded' }\n case ERROR_CODES.STREAMING_ERROR:\n return { type: 'streaming_error' }\n default:\n return null\n }\n}\n\n/**\n * Classifies a caught error from the Content Agent API into a known error type.\n * Useful for clients that need to take different actions per error (e.g. show\n * resource-selection buttons). For the user-facing message, use {@link getAgentErrorMessage}.\n *\n * @category Utilities\n */\nexport function classifyAgentError(error: unknown): AgentErrorType {\n // Handle SSE stream errors (from parseStreamLine)\n if (error instanceof SanityAgentStreamError && error.code) {\n return mapErrorCode(error.code) ?? { type: 'unknown' }\n }\n\n // Handle HTTP-level errors (from fetchApi)\n if (error instanceof SanityAgentError) {\n if (error.status !== 400) {\n return { type: 'unknown' }\n }\n return mapErrorCode(error.body.code) ?? { type: 'unknown' }\n }\n\n return { type: 'unknown' }\n}\n\nexport function isAppRelatedError(classified: AgentErrorType): boolean {\n return classified.type === 'no_applications' || classified.type === 'application_not_found'\n}\n\nexport type AgentErrorMessageContext = {\n orgName?: string | null\n}\n\n/**\n * Resolves a user-facing error message for a classified agent error.\n * For errors that benefit from additional context (e.g. org name), the optional\n * {@link resolveContext} callback is invoked lazily — only when the error type\n * needs it. Non-contextual errors return the backend message as-is.\n *\n * @category Utilities\n */\nexport async function resolveAgentErrorMessage(\n classified: AgentErrorType,\n error: unknown,\n resolveContext?: () => Promise<AgentErrorMessageContext>,\n): Promise<string> {\n const fallback = getAgentErrorMessage(error)\n\n if (!resolveContext || !isAppRelatedError(classified)) return fallback\n\n const ctx = await resolveContext()\n if (!ctx.orgName) return fallback\n\n switch (classified.type) {\n case 'no_applications':\n return `Studio action required: To use the Content Agent in \"${ctx.orgName}\", you must have at least one Sanity Studio running version 5.1.0 or above. After upgrading, open your studio to connect it to the agent.`\n case 'application_not_found':\n return `The requested studio is not available in \"${ctx.orgName}\". You may not have access, or it may belong to a different organization.`\n default:\n return fallback\n }\n}\n\n/**\n * Extracts a user-facing error message from an agent API error.\n * Returns the message sent by the API when available, otherwise a generic fallback.\n *\n * @category Utilities\n */\nexport function getAgentErrorMessage(error: unknown): string {\n if (error instanceof SanityAgentStreamError) {\n return error.message\n }\n if (error instanceof SanityAgentError && error.body.message) {\n return error.body.message\n }\n return GENERIC_ERROR_MESSAGE\n}\n\nexport interface FetchOptions {\n url: string\n body: unknown\n headers: Record<string, string>\n credentials?: RequestCredentials\n signal?: AbortSignal\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Makes a POST request and returns the response.\n * Throws SanityAgentError if the response is not ok.\n *\n * @category Utilities\n */\nexport async function fetchApi(options: FetchOptions): Promise<Response> {\n const fetchFn = options.fetch ?? globalThis.fetch\n\n const response = await fetchFn(options.url, {\n method: 'POST',\n headers: options.headers,\n body: JSON.stringify(options.body),\n signal: options.signal,\n ...(options.credentials && { credentials: options.credentials }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SanityAgentError(response.status, errorText)\n }\n\n return response\n}\n\n/**\n * Extracts text content from message parts (user or assistant).\n *\n * @category Utilities\n */\nexport function extractTextFromParts(\n content: string | Array<{ type: string; text?: string }>,\n): string {\n if (typeof content === 'string') {\n return content\n }\n\n return content\n .filter(\n (part): part is { type: 'text'; text: string } =>\n part.type === 'text' && typeof part.text === 'string',\n )\n .map((part) => part.text)\n .join('\\n')\n}\n\n/**\n * Creates an empty V3 usage object. Our API does not return token counts.\n *\n * @category Utilities\n */\nexport function createEmptyUsage(): LanguageModelV3Usage {\n return {\n inputTokens: {\n total: 0,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: {\n total: 0,\n text: undefined,\n reasoning: undefined,\n },\n }\n}\n\n/**\n * Maps API finish reasons to LanguageModelV3FinishReason.\n *\n * @category Utilities\n */\nexport function mapFinishReason(reason: string | undefined): LanguageModelV3FinishReason {\n const unified = (() => {\n switch (reason) {\n case 'stop':\n case 'length':\n case 'content-filter':\n case 'tool-calls':\n case 'error':\n return reason\n default:\n return 'other' as const\n }\n })()\n\n return { unified, raw: reason }\n}\n\nexport type ParsedStreamLine =\n | { kind: 'part'; part: LanguageModelV3StreamPart }\n | { kind: 'finish'; finishReason: LanguageModelV3FinishReason }\n | { kind: 'data'; type: string; data: unknown }\n\n/**\n * Parses a single SSE line from the AI SDK v6 UI Message Stream protocol.\n * Returns null if the line cannot be parsed or should be ignored.\n *\n * Format: `data: <json>\\n`\n *\n * Supported types:\n * text-start – Text block start (forwarded as-is)\n * text-delta – Text chunk\n * text-end – Text block end (forwarded as-is)\n * tool-input-available – Tool call (client tools only, without providerExecuted)\n * tool-output-available – Tool result (informational, skipped)\n * finish – Finish event with reason\n * error – Error event\n * start – Stream start (skipped)\n * [DONE] – Stream termination (skipped)\n *\n * @see https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol\n *\n * @param line A single line from the SSE byte stream.\n * @param textContentId Fallback ID for text parts that lack an explicit id.\n *\n * @category Utilities\n */\nexport function parseStreamLine(line: string, textContentId: string): ParsedStreamLine | null {\n const trimmed = line.trim()\n if (!trimmed.startsWith('data: ')) return null\n\n const payload = trimmed.slice(6)\n if (payload === '[DONE]') return null\n\n let part: Record<string, unknown>\n try {\n part = JSON.parse(payload) as Record<string, unknown>\n } catch {\n return null\n }\n\n const type = part.type as string | undefined\n if (!type) return null\n\n switch (type) {\n case 'text-start': {\n const id = (part.id as string) || textContentId\n return { kind: 'part', part: { type: 'text-start', id } }\n }\n\n case 'text-delta': {\n const id = (part.id as string) || textContentId\n const delta = part.delta as string\n if (typeof delta !== 'string') return null\n return { kind: 'part', part: { type: 'text-delta', id, delta } }\n }\n\n case 'text-end': {\n const id = (part.id as string) || textContentId\n return { kind: 'part', part: { type: 'text-end', id } }\n }\n\n case 'tool-input-available': {\n // Forward both client- and server-executed tools to the consumer's\n // streamText loop. Stop conditions like `hasToolCall('final_answer')`\n // need to see the terminal `providerExecuted` tool call to fire; the\n // consumer's local-execute path skips tools without an `execute`\n // function (or that are not in the consumer's tool map), so a server\n // tool surfaced here cannot be double-executed. See SAGE-182.\n //\n // Server-executed tools (`providerExecuted: true`) are also marked\n // `dynamic: true`. The AI SDK's `parseToolCall` only accepts a\n // tool that is missing from the consumer's tool map when both\n // `providerExecuted` and `dynamic` are set — otherwise it throws\n // `NoSuchToolError` and emits a phantom `tool-error` chunk for a\n // tool that already executed server-side. The agent server's tools\n // are not exposed to the consumer at all (server-execute is the\n // whole point), so `dynamic` is the right flag to set here.\n const input = part.input\n const isServerExecuted = part.providerExecuted === true\n return {\n kind: 'part',\n part: {\n type: 'tool-call',\n toolCallId: part.toolCallId as string,\n toolName: part.toolName as string,\n input: typeof input === 'string' ? input : JSON.stringify(input),\n ...(isServerExecuted && { providerExecuted: true, dynamic: true }),\n },\n }\n }\n\n case 'tool-output-available': {\n // Forward the result of a provider-executed tool (e.g. `final_answer`)\n // so the AI SDK loop can pair it with the matching `tool-call` and\n // include it in `currentModelResponse.content`. Without the result\n // chunk the loop will request another step to satisfy the dangling\n // tool call, defeating the natural termination Phase 1 enables.\n //\n // Only forward when `providerExecuted` is set. Client-tool results\n // are produced locally on the consumer side via `tool.execute`, not\n // streamed from the server, so any `tool-output-available` we see\n // for a non-providerExecuted tool would be unexpected — drop it.\n if (part.providerExecuted !== true) return null\n // The AI SDK's `tool-result` part requires a non-null JSON-serializable\n // result. Fall back to an empty object when the server omits one (e.g.\n // tools that signal completion via side effects).\n const result = (part.output ?? {}) as NonNullable<typeof part.output>\n return {\n kind: 'part',\n part: {\n type: 'tool-result',\n toolCallId: part.toolCallId as string,\n toolName: part.toolName as string,\n result,\n dynamic: true,\n },\n }\n }\n\n case 'error': {\n const errorText = (part.errorText as string) ?? 'Unknown error'\n const errorCode = part.errorCode as string | undefined\n return {\n kind: 'part',\n part: {\n type: 'error',\n error: new SanityAgentStreamError(errorText, errorCode),\n },\n }\n }\n\n case 'abort': {\n return { kind: 'data', type: 'abort', data: { reason: part.reason } }\n }\n\n case 'finish': {\n return {\n kind: 'finish',\n finishReason: mapFinishReason(part.finishReason as string | undefined),\n }\n }\n\n default: {\n if (type.startsWith('data-')) {\n return { kind: 'data', type: type.slice(5), data: part.data }\n }\n return null\n }\n }\n}\n\n/**\n * Extracts accumulated text from a streaming response body.\n * Used for simple streaming cases where only the final text is needed.\n *\n * @category Utilities\n */\nexport async function extractTextFromStream(\n responseBody: ReadableStream<Uint8Array>,\n): Promise<string> {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n const textContentId = 'extract'\n let text = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n for (const line of chunk.split('\\n')) {\n const parsed = parseStreamLine(line, textContentId)\n if (parsed?.kind === 'part' && parsed.part.type === 'text-delta') {\n text += parsed.part.delta\n }\n }\n }\n\n return text\n}\n\n/**\n * Creates a ReadableStream that parses AI SDK v6 UI Message Stream SSE data\n * from a response body and emits LanguageModelV3 stream parts.\n *\n * Text lifecycle events (text-start / text-end) are forwarded from the server.\n * Tool calls are tracked to ensure the finish reason is set correctly.\n *\n * @category Utilities\n */\nexport function createSSEStream(\n responseBody: ReadableStream<Uint8Array>,\n warnings: SharedV3Warning[],\n onData?: (type: string, data: unknown) => void,\n): ReadableStream<LanguageModelV3StreamPart> {\n let finishReason: LanguageModelV3FinishReason = { unified: 'other', raw: undefined }\n const fallbackTextId = crypto.randomUUID()\n let hasToolCalls = false\n\n return new ReadableStream<LanguageModelV3StreamPart>({\n async start(controller) {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n controller.enqueue({ type: 'stream-start', warnings })\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n const parsed = parseStreamLine(line, fallbackTextId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n }\n controller.enqueue(parsed.part)\n } else if (parsed?.kind === 'finish') {\n finishReason = parsed.finishReason\n } else if (parsed?.kind === 'data') {\n onData?.(parsed.type, parsed.data)\n }\n }\n }\n\n if (buffer.trim()) {\n const parsed = parseStreamLine(buffer, fallbackTextId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n }\n controller.enqueue(parsed.part)\n } else if (parsed?.kind === 'finish') {\n finishReason = parsed.finishReason\n } else if (parsed?.kind === 'data') {\n onData?.(parsed.type, parsed.data)\n }\n }\n\n // If tool calls were emitted, override finish reason so streamText\n // knows to execute tools and continue with the next step.\n if (hasToolCalls && finishReason.unified !== 'tool-calls') {\n finishReason = { unified: 'tool-calls', raw: finishReason.raw ?? 'tool-calls' }\n }\n\n controller.enqueue({\n type: 'finish',\n finishReason,\n usage: createEmptyUsage(),\n })\n } catch (error) {\n controller.enqueue({\n type: 'error',\n error: error instanceof Error ? error : new Error(String(error)),\n })\n } finally {\n controller.close()\n }\n },\n })\n}\n","import type {\n LanguageModelV3,\n LanguageModelV3CallOptions,\n LanguageModelV3Content,\n LanguageModelV3GenerateResult,\n LanguageModelV3Prompt,\n LanguageModelV3StreamResult,\n SharedV3Warning,\n} from '@ai-sdk/provider'\nimport type { ClientToolDefinition } from '@sanity/agent-types'\nimport { groupBy } from 'es-toolkit'\nimport type { AgentSettings, Message } from './types'\nimport {\n buildApiUrl,\n buildHeaders,\n createEmptyUsage,\n createSSEStream,\n extractTextFromParts,\n fetchApi,\n parseStreamLine,\n} from './utils'\n\n/**\n * @category Model\n */\nexport interface ContentAgentModelConfig {\n provider: string\n baseURL: string\n apiVersion: string\n organizationId: string\n headers: () => Record<string, string | undefined>\n credentials?: RequestCredentials\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Extended message type for the wire format that supports tool calls and tool results\n * in addition to plain text messages.\n */\ntype WireMessage =\n | Message\n | {\n role: 'assistant'\n content: Array<\n | { type: 'text'; text: string }\n | { type: 'tool-call'; toolCallId: string; toolName: string; input: string }\n >\n }\n | {\n role: 'tool'\n content: Array<{\n type: 'tool-result'\n toolCallId: string\n toolName: string\n output: unknown\n }>\n }\n\nconst UNSUPPORTED_SETTINGS = [\n 'maxOutputTokens',\n 'temperature',\n 'topP',\n 'topK',\n 'frequencyPenalty',\n 'presencePenalty',\n 'seed',\n] as const\n\n/**\n * Vercel AI SDK `LanguageModelV1` implementation that communicates with the\n * Sanity Content Agent API via a conversation thread.\n *\n * Typically you don't construct this directly — use\n * {@link createContentAgent | createContentAgent().agent(threadId)} instead.\n *\n * @category Model\n *\n * @example\n * ```ts\n * import { createContentAgent } from 'content-agent'\n * import { streamText } from 'ai'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n */\nexport class ContentAgentModel implements LanguageModelV3 {\n readonly specificationVersion = 'v3' as const\n readonly provider: string\n readonly modelId: string\n readonly supportedUrls: Record<string, RegExp[]> = {}\n\n private readonly config: ContentAgentModelConfig\n private readonly threadId: string\n private readonly settings: AgentSettings\n\n constructor(\n threadId: string,\n settings: AgentSettings | undefined,\n config: ContentAgentModelConfig,\n ) {\n this.threadId = threadId\n this.modelId = `content-agent:${threadId}`\n this.provider = config.provider\n this.settings = settings ?? {}\n this.config = config\n }\n\n /**\n * Converts a LanguageModelV3Prompt into wire messages for the agent API.\n *\n * Handles all message roles:\n * - system: plain text system messages\n * - user: extracts text from content parts\n * - assistant: extracts text and/or tool-call parts\n * - tool: converts tool-result parts for the API\n */\n private convertPromptToMessages(prompt: LanguageModelV3Prompt): WireMessage[] {\n const messages: WireMessage[] = []\n\n for (const message of prompt) {\n if (message.role === 'system') {\n if (message.content) {\n messages.push({ role: 'system', content: message.content })\n }\n continue\n }\n\n if (message.role === 'tool') {\n const toolResults = message.content\n .filter((part) => part.type === 'tool-result')\n .map((part) => {\n const p = part as {\n toolCallId: string\n toolName: string\n output: { type: string; value: unknown } | unknown\n }\n return {\n type: 'tool-result' as const,\n toolCallId: p.toolCallId,\n toolName: p.toolName,\n output:\n p.output && typeof p.output === 'object' && 'value' in p.output\n ? (p.output as { value: unknown }).value\n : p.output,\n }\n })\n\n if (toolResults.length > 0) {\n messages.push({ role: 'tool', content: toolResults })\n }\n continue\n }\n\n if (message.role === 'assistant') {\n const content = message.content as Array<{\n type: string\n text?: string\n toolCallId?: string\n toolName?: string\n input?: string\n }>\n\n const grouped = groupBy(content, (p) => p.type)\n const toolCalls = grouped['tool-call'] ?? []\n const textParts = grouped.text ?? []\n\n if (toolCalls.length > 0) {\n const parts = [\n ...textParts\n .filter((p) => p.text)\n .map((p) => ({ type: 'text' as const, text: p.text! })),\n ...toolCalls.map((tc) => ({\n type: 'tool-call' as const,\n toolCallId: tc.toolCallId!,\n toolName: tc.toolName!,\n input: tc.input!,\n })),\n ]\n\n if (parts.length > 0) {\n messages.push({ role: 'assistant', content: parts })\n }\n } else {\n const text = extractTextFromParts(content as Array<{ type: string; text?: string }>)\n if (text) {\n messages.push({ role: 'assistant', content: text })\n }\n }\n continue\n }\n\n // user messages\n const text = extractTextFromParts(message.content as Array<{ type: string; text?: string }>)\n if (text) {\n messages.push({ role: message.role, content: text })\n }\n }\n\n return messages\n }\n\n /**\n * Extracts client tool definitions from the model call options.\n * Only the schemas are sent — execute functions stay on the client.\n */\n private extractClientTools(\n options: LanguageModelV3CallOptions,\n ): ClientToolDefinition[] | undefined {\n if (!options.tools?.length) return undefined\n\n const clientTools = options.tools\n .filter((t) => t.type === 'function')\n .map((t) => ({\n type: 'function' as const,\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema as Record<string, unknown>,\n }))\n\n return clientTools.length > 0 ? clientTools : undefined\n }\n\n private collectWarnings(options: LanguageModelV3CallOptions): SharedV3Warning[] {\n const warnings: SharedV3Warning[] = []\n\n for (const setting of UNSUPPORTED_SETTINGS) {\n if (options[setting] !== undefined) {\n warnings.push({ type: 'unsupported', feature: setting })\n }\n }\n\n if (options.stopSequences?.length) {\n warnings.push({ type: 'unsupported', feature: 'stopSequences' })\n }\n\n if (options.responseFormat && options.responseFormat.type !== 'text') {\n warnings.push({ type: 'unsupported', feature: 'responseFormat' })\n }\n\n return warnings\n }\n\n private buildRequestBody(options: LanguageModelV3CallOptions, stream: boolean) {\n const clientTools = this.extractClientTools(options)\n const toolChoice = options.toolChoice\n\n return {\n messages: this.convertPromptToMessages(options.prompt),\n stream,\n ...(clientTools && { clientTools }),\n ...(toolChoice && { toolChoice }),\n ...(this.settings.application && { application: this.settings.application }),\n ...(this.settings.config && { config: this.settings.config }),\n ...(this.settings.format && { format: this.settings.format }),\n ...(this.settings.clientId && { clientId: this.settings.clientId }),\n }\n }\n\n private getResponseHeaders(response: Response): Record<string, string> {\n return Object.fromEntries(response.headers.entries())\n }\n\n async doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult> {\n // Always request a stream from the backend and buffer the result.\n // This keeps the HTTP connection alive with incremental data, avoiding\n // 504 gateway timeouts on long-running agent requests.\n const body = this.buildRequestBody(options, true)\n const warnings = this.collectWarnings(options)\n\n const response = await fetchApi({\n url: buildApiUrl(\n this.config.baseURL,\n this.config.apiVersion,\n `/${this.config.organizationId}/threads/${this.threadId}`,\n ),\n body,\n headers: buildHeaders(this.config.headers()),\n credentials: this.config.credentials,\n signal: options.abortSignal,\n fetch: this.config.fetch,\n })\n\n if (!response.body) {\n throw new Error('No response body for generate request')\n }\n\n const { content, finishReason } = await this.extractContentFromStream(response.body)\n\n return {\n content,\n finishReason,\n usage: createEmptyUsage(),\n request: { body },\n response: { headers: this.getResponseHeaders(response) },\n warnings,\n }\n }\n\n /**\n * Parses a streamed response body into content parts (text and tool calls)\n * and a finish reason. Used by doGenerate to buffer the full response.\n */\n private async extractContentFromStream(responseBody: ReadableStream<Uint8Array>): Promise<{\n content: LanguageModelV3Content[]\n finishReason: LanguageModelV3GenerateResult['finishReason']\n }> {\n const reader = responseBody.getReader()\n const decoder = new TextDecoder()\n const textContentId = 'generate'\n let text = ''\n let hasToolCalls = false\n const content: LanguageModelV3Content[] = []\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n const chunk = decoder.decode(value, { stream: true })\n for (const line of chunk.split('\\n')) {\n const parsed = parseStreamLine(line, textContentId)\n if (parsed?.kind === 'part') {\n if (parsed.part.type === 'text-delta') {\n text += parsed.part.delta\n } else if (parsed.part.type === 'tool-call') {\n hasToolCalls = true\n const tc = parsed.part as { toolCallId: string; toolName: string; input: string }\n content.push({\n type: 'tool-call',\n toolCallId: tc.toolCallId,\n toolName: tc.toolName,\n input: tc.input,\n })\n }\n }\n }\n }\n\n if (text) {\n content.unshift({ type: 'text', text })\n }\n\n const finishReason = hasToolCalls\n ? { unified: 'tool-calls' as const, raw: 'tool-calls' }\n : { unified: 'stop' as const, raw: undefined }\n\n return { content, finishReason }\n }\n\n async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {\n const body = this.buildRequestBody(options, true)\n const warnings = this.collectWarnings(options)\n\n const response = await fetchApi({\n url: buildApiUrl(\n this.config.baseURL,\n this.config.apiVersion,\n `/${this.config.organizationId}/threads/${this.threadId}`,\n ),\n body,\n headers: buildHeaders(this.config.headers()),\n credentials: this.config.credentials,\n signal: options.abortSignal,\n fetch: this.config.fetch,\n })\n\n if (!response.body) {\n throw new Error('No response body for streaming request')\n }\n\n return {\n stream: createSSEStream(response.body, warnings, this.settings.onStreamData),\n request: { body },\n response: { headers: this.getResponseHeaders(response) },\n }\n }\n}\n","import type { LanguageModelV3 } from '@ai-sdk/provider'\nimport type { SanityClientConfig } from './from-client'\nimport { ContentAgentModel, type ContentAgentModelConfig } from './sanity-agent-language-model'\nimport type {\n AgentSettings,\n Application,\n ContentAgentProviderSettings,\n GenerateOptions,\n GenerateResult,\n GenerateSettings,\n} from './types'\nimport {\n buildApiUrl,\n buildHeaders,\n extractTextFromStream,\n fetchApi,\n SanityAgentError,\n} from './utils'\n\nconst DEFAULT_BASE_URL = 'https://api.sanity.io'\nconst DEFAULT_API_VERSION = 'v2026-01-01'\n\n/**\n * A Content Agent provider instance returned by {@link createContentAgent}.\n *\n * Provides two modes of interaction:\n * - **Conversation threads** via {@link ContentAgentProvider.agent | .agent()} — stateful, multi-turn conversations with server-side message history.\n * - **One-shot prompts** via {@link ContentAgentProvider.prompt | .prompt()} — stateless, single-turn interactions.\n *\n * @category Provider\n */\nexport interface ContentAgentProvider {\n /**\n * Creates a Vercel AI SDK {@link https://sdk.vercel.ai/docs/ai-sdk-core | LanguageModelV1} for a conversation thread.\n *\n * The thread ID identifies the conversation and maintains message history server-side.\n * If a thread with that ID already exists, the conversation continues. If not, a new\n * thread is automatically created.\n *\n * Use the returned model with `generateText` or `streamText` from the `ai` package.\n *\n * @example\n * ```ts\n * import { generateText, streamText } from 'ai'\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * // Non-streaming\n * const { text } = await generateText({ model, prompt: 'What blog posts do I have?' })\n *\n * // Streaming\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n *\n * @param threadId - Unique identifier for the conversation thread.\n * @param settings - Optional agent settings (application, capabilities, filters, etc.).\n */\n agent(threadId: string, settings?: AgentSettings): LanguageModelV3\n\n /**\n * Lists available applications for the organization.\n *\n * Returns all Sanity Studio applications the authenticated user has access to.\n * Use the `key` from a returned application to target it when creating an agent.\n *\n * @example\n * ```ts\n * const apps = await contentAgent.applications()\n * const app = apps.find((app) => app.title === \"My Studio\")\n *\n * const model = contentAgent.agent(\"my-thread\", {\n * application: { key: app.key },\n * })\n * ```\n */\n applications(): Promise<Application[]>\n\n /**\n * Matches a Sanity client's `projectId` and `dataset` against the org's application list.\n * Throws if no match is found. Prefer {@link createStudioAgent} which calls this automatically.\n */\n resolveApplication(client: SanityClientConfig, workspace: string): Promise<Application>\n\n /**\n * Sends a one-shot prompt to the Content Agent (stateless).\n *\n * Unlike {@link ContentAgentProvider.agent | .agent()}, this does not create or use a conversation thread.\n * Each call is independent with no message history or persistence.\n * Ideal for simple, single-turn interactions.\n *\n * @example\n * ```ts\n * const { text } = await contentAgent.prompt(\n * {\n * application: { key: 'projectId.datasetName' },\n * config: { capabilities: { read: true, write: false } },\n * instructions: 'Be concise',\n * },\n * { message: 'List my 5 most recent posts' },\n * )\n * ```\n *\n * @param settings - Generation settings (application, capabilities, instructions, etc.).\n * @param options - The prompt message and request options.\n */\n prompt(settings: GenerateSettings, options: GenerateOptions): Promise<GenerateResult>\n}\n\n/**\n * Creates a Content Agent provider instance.\n *\n * The provider is the main entry point for interacting with the Sanity Content Agent API.\n * It returns an object with methods for conversation threads and one-shot prompts.\n *\n * @category Provider\n *\n * @example Basic setup\n * ```ts\n * import { createContentAgent } from 'content-agent'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n * ```\n *\n * @example Using with Vercel AI SDK for conversation threads\n * ```ts\n * import { createContentAgent } from 'content-agent'\n * import { generateText, streamText } from 'ai'\n *\n * const contentAgent = createContentAgent({\n * organizationId: 'your-org-id',\n * token: 'your-sanity-token',\n * })\n *\n * const model = contentAgent.agent('my-thread-id', {\n * application: { key: 'projectId.datasetName' },\n * config: {\n * capabilities: { read: true, write: false },\n * },\n * })\n *\n * // Non-streaming\n * const { text } = await generateText({ model, prompt: 'What blog posts do I have?' })\n *\n * // Streaming\n * const { textStream } = await streamText({ model, prompt: 'Summarize my content' })\n * for await (const chunk of textStream) {\n * process.stdout.write(chunk)\n * }\n * ```\n *\n * @example One-shot generation (stateless, no thread)\n * ```ts\n * const { text } = await contentAgent.prompt(\n * {\n * application: { key: 'projectId.datasetName' },\n * config: { capabilities: { read: true, write: false } },\n * instructions: 'Be concise',\n * },\n * { message: 'List my 5 most recent posts' },\n * )\n * ```\n *\n * @param options - Provider configuration including organization ID, token, and optional overrides.\n */\nexport function createContentAgent(options: ContentAgentProviderSettings): ContentAgentProvider {\n const baseURL = options.baseURL?.replace(/\\/$/, '') ?? DEFAULT_BASE_URL\n const apiVersion = options.apiVersion ?? DEFAULT_API_VERSION\n const { organizationId } = options\n\n const getHeaders = (): Record<string, string | undefined> => ({\n ...(options.token && { Authorization: `Bearer ${options.token}` }),\n ...options.headers,\n })\n\n // When no token is provided, include credentials so the browser sends\n // cookies automatically — this is the Studio / browser auth path.\n const credentials: RequestCredentials | undefined = options.token ? undefined : 'include'\n\n const config: ContentAgentModelConfig = {\n provider: 'content-agent',\n baseURL,\n apiVersion,\n organizationId,\n headers: getHeaders,\n credentials,\n fetch: options.fetch,\n }\n\n const agent = (threadId: string, settings?: AgentSettings): LanguageModelV3 => {\n return new ContentAgentModel(\n threadId,\n {\n ...settings,\n application: settings?.application ?? options.application,\n },\n config,\n )\n }\n\n let applicationsPromise: Promise<Application[]> | null = null\n\n const applications = (): Promise<Application[]> => {\n if (!applicationsPromise) {\n applicationsPromise = (async () => {\n const fetchFn = options.fetch ?? globalThis.fetch\n const url = buildApiUrl(baseURL, apiVersion, `/${organizationId}/applications`)\n const headers = buildHeaders(getHeaders())\n\n const response = await fetchFn(url, {\n method: 'GET',\n headers,\n ...(credentials && { credentials }),\n })\n\n if (!response.ok) {\n applicationsPromise = null\n const errorText = await response.text()\n throw new SanityAgentError(response.status, errorText)\n }\n\n const json = (await response.json()) as { applications?: Application[] }\n return json.applications ?? []\n })()\n }\n\n return applicationsPromise\n }\n\n const prompt = async (\n settings: GenerateSettings,\n generateOptions: GenerateOptions,\n ): Promise<GenerateResult> => {\n const effectiveApplication = settings.application ?? options.application\n\n const body = {\n organizationId,\n message: generateOptions.message,\n stream: generateOptions.stream ?? false,\n ...(effectiveApplication && { application: effectiveApplication }),\n ...(settings.config && { config: settings.config }),\n ...(settings.instructions && { instructions: settings.instructions }),\n ...(settings.format && { format: settings.format }),\n }\n\n const response = await fetchApi({\n url: buildApiUrl(baseURL, apiVersion, '/prompt'),\n body,\n headers: buildHeaders(getHeaders()),\n credentials,\n signal: generateOptions.abortSignal,\n fetch: options.fetch,\n })\n\n if (body.stream) {\n if (!response.body) {\n throw new SanityAgentError(500, 'No response body')\n }\n return { text: await extractTextFromStream(response.body) }\n }\n\n const json = (await response.json()) as { text?: string }\n return { text: json.text ?? '' }\n }\n\n const resolveApplication = async (\n client: SanityClientConfig,\n workspace: string,\n ): Promise<Application> => {\n const { projectId, dataset } = client.config()\n if (!projectId || !dataset) {\n throw new Error(\n 'resolveApplication: client is missing `projectId` or `dataset` in its config',\n )\n }\n\n const resourceId = `${projectId}.${dataset}`\n const apps = await applications()\n const match = apps.find((app) => app.resource?.id === resourceId && app.name === workspace)\n\n if (!match) {\n throw new SanityAgentError(\n 400,\n JSON.stringify({\n code: 'APPLICATION_NOT_FOUND',\n message: `No application found for resource \"${resourceId}\" with workspace \"${workspace}\". Make sure the Studio has been deployed and belongs to this organization.`,\n }),\n )\n }\n\n return match\n }\n\n return { agent, applications, resolveApplication, prompt }\n}\n","import type { LanguageModelV3, LanguageModelV3CallOptions } from '@ai-sdk/provider'\nimport { getBaseUrl, type SanityClientConfig, type SanityClientLike } from './from-client'\nimport type { ContentAgentProvider } from './sanity-agent-provider'\nimport { createContentAgent } from './sanity-agent-provider'\nimport type { AgentSettings, ContentAgentProviderSettings } from './types'\nimport { buildHeaders, SanityAgentError } from './utils'\n\nconst PROJECTS_API_VERSION = 'v2024-01-01'\n\n/**\n * Module-level cache for setup promises. Keyed by `projectId.dataset.workspace`,\n * so repeated calls to `createStudioAgent` with the same params (e.g. React strict\n * mode double-render, component remounts) reuse the same in-flight or resolved setup.\n */\nconst providerCache = new Map<string, Promise<ContentAgentProvider>>()\n\ntype ProjectResponse = {\n id: string\n organizationId?: string | null\n}\n\n/**\n * Options for {@link createStudioAgent}.\n */\nexport type StudioAgentOptions = {\n /** Custom fetch implementation. Useful for testing. */\n fetch?: typeof globalThis.fetch\n\n /**\n * Additional provider settings forwarded to {@link createContentAgent}.\n * `organizationId`, `baseURL`, and `application` are derived automatically.\n */\n providerOptions?: Omit<ContentAgentProviderSettings, 'organizationId' | 'baseURL' | 'application'>\n}\n\nasync function fetchOrganizationId(\n baseURL: string,\n projectId: string,\n fetchFn: typeof globalThis.fetch,\n): Promise<string> {\n const url = `${baseURL}/${PROJECTS_API_VERSION}/projects/${projectId}`\n\n const response = await fetchFn(url, {\n method: 'GET',\n headers: buildHeaders({}),\n credentials: 'include',\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new SanityAgentError(\n response.status,\n JSON.stringify({\n code: 'PROJECT_FETCH_FAILED',\n message: `Failed to fetch project \"${projectId}\": ${errorText}`,\n }),\n )\n }\n\n const project = (await response.json()) as ProjectResponse\n\n if (!project.organizationId) {\n throw new SanityAgentError(\n 400,\n JSON.stringify({\n code: 'NO_ORGANIZATION',\n message: `Project \"${projectId}\" is not assigned to an organization. Content Agent requires an organization-level project.`,\n }),\n )\n }\n\n return project.organizationId\n}\n\n/**\n * Resolves the full provider by fetching org ID and application. The returned\n * promise is cached so the setup only runs once.\n */\nfunction initProvider(\n projectId: string,\n dataset: string,\n workspace: string,\n baseURL: string,\n fetchFn: typeof globalThis.fetch,\n providerOptions?: StudioAgentOptions['providerOptions'],\n): Promise<ContentAgentProvider> {\n return fetchOrganizationId(baseURL, projectId, fetchFn).then(async (organizationId) => {\n const tempProvider = createContentAgent({\n organizationId,\n baseURL,\n fetch: fetchFn,\n })\n\n const client: SanityClientConfig = { config: () => ({ projectId, dataset }) }\n const application = await tempProvider.resolveApplication(client, workspace)\n\n return createContentAgent({\n ...providerOptions,\n organizationId,\n baseURL,\n application,\n })\n })\n}\n\n/**\n * A {@link LanguageModelV3} that defers to a lazily-resolved provider.\n * The first call to `doGenerate` or `doStream` awaits the setup promise,\n * then delegates to the real model.\n */\nclass LazyContentAgentModel implements LanguageModelV3 {\n readonly specificationVersion = 'v3' as const\n readonly provider = 'content-agent'\n readonly modelId: string\n readonly supportedUrls: Record<string, RegExp[]> = {}\n\n private readonly providerPromise: Promise<ContentAgentProvider>\n private readonly threadId: string\n private readonly settings: AgentSettings | undefined\n private resolvedModel: LanguageModelV3 | null = null\n\n constructor(\n threadId: string,\n settings: AgentSettings | undefined,\n providerPromise: Promise<ContentAgentProvider>,\n ) {\n this.threadId = threadId\n this.settings = settings\n this.modelId = `content-agent:${threadId}`\n this.providerPromise = providerPromise\n }\n\n private async getModel(): Promise<LanguageModelV3> {\n if (!this.resolvedModel) {\n const provider = await this.providerPromise\n this.resolvedModel = provider.agent(this.threadId, this.settings)\n }\n return this.resolvedModel\n }\n\n async doGenerate(options: LanguageModelV3CallOptions) {\n const model = await this.getModel()\n return model.doGenerate(options)\n }\n\n async doStream(options: LanguageModelV3CallOptions) {\n const model = await this.getModel()\n return model.doStream(options)\n }\n}\n\n/**\n * Creates a Content Agent provider fully configured for a Studio context.\n *\n * The call is **synchronous** — safe for React components and `useMemo`.\n * Async setup (org ID + application resolution) is deferred to the first API call.\n *\n * @param client - A Sanity client instance. Available via `useClient()` in Studio.\n * @param workspace - The active workspace name. Available via `useWorkspace().name`.\n * @param options - Optional overrides (custom fetch, additional provider settings).\n */\nexport function createStudioAgent(\n client: SanityClientLike,\n workspace: string,\n options?: StudioAgentOptions,\n): ContentAgentProvider {\n const { projectId, dataset } = client.config()\n\n if (!projectId) {\n throw new Error('createStudioAgent: client is missing `projectId` in its config')\n }\n if (!dataset) {\n throw new Error('createStudioAgent: client is missing `dataset` in its config')\n }\n\n const fetchFn = options?.fetch ?? globalThis.fetch\n const baseURL = getBaseUrl(client)\n const cacheKey = `${projectId}.${dataset}.${workspace}`\n\n // Reuse an existing setup promise for the same project/dataset/workspace,\n // or start a new one. Custom fetch or providerOptions bypass the cache\n // since they indicate a non-default configuration (e.g. tests).\n const useCache = !options?.fetch && !options?.providerOptions\n\n let providerPromise: Promise<ContentAgentProvider>\n\n if (useCache && providerCache.has(cacheKey)) {\n providerPromise = providerCache.get(cacheKey)!\n } else {\n providerPromise = initProvider(\n projectId,\n dataset,\n workspace,\n baseURL,\n fetchFn,\n options?.providerOptions,\n )\n\n if (useCache) {\n providerCache.set(cacheKey, providerPromise)\n\n // Evict on failure so a transient error doesn't stick forever\n providerPromise.catch(() => {\n providerCache.delete(cacheKey)\n })\n }\n }\n\n const awaitProvider = async <T>(fn: (provider: ContentAgentProvider) => Promise<T>): Promise<T> =>\n fn(await providerPromise)\n\n return {\n agent(threadId: string, settings?: AgentSettings): LanguageModelV3 {\n return new LazyContentAgentModel(threadId, settings, providerPromise)\n },\n\n applications() {\n return awaitProvider((p) => p.applications())\n },\n\n resolveApplication(resolveClient: SanityClientConfig, resolveWorkspace: string) {\n return awaitProvider((p) => p.resolveApplication(resolveClient, resolveWorkspace))\n },\n\n prompt(settings, generateOptions) {\n return awaitProvider((p) => p.prompt(settings, generateOptions))\n },\n }\n}\n"],"names":["text"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-agent",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Vercel AI SDK provider for Sanity Content Agent API",
5
5
  "keywords": [
6
6
  "sanity",
@@ -34,12 +34,12 @@
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.0.101"
37
+ "@sanity/agent-types": "0.0.102"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@sanity/pkg-utils": "^10.4.1",
41
41
  "@types/node": "^22.19.7",
42
- "ai": "^6.0.34",
42
+ "ai": "^6.0.168",
43
43
  "openapi-typescript": "^7.10.1",
44
44
  "typedoc": "^0.28.17",
45
45
  "typescript": "^5.9.3",