content-agent 0.7.6 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -66
- package/dist/_chunks-dts/sanity-agent-provider.d.ts +980 -0
- package/dist/_chunks-dts/sanity-agent-provider.d.ts.map +1 -0
- package/dist/content-agent.d.ts +64 -0
- package/dist/content-agent.d.ts.map +1 -0
- package/dist/content-agent.js +56 -0
- package/dist/content-agent.js.map +1 -0
- package/dist/index.d.ts +2 -978
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanity-agent-provider.d.ts","names":[],"sources":["../../src/generated/openapi.ts","../../src/types.ts","../../src/from-client.ts","../../src/sanity-agent-provider.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { c as AgentSettings, t as ContentAgentProvider } from "./_chunks-dts/sanity-agent-provider.js";
|
|
2
|
+
import { Output, ToolLoopAgent, ToolLoopAgentSettings, ToolSet } from "ai";
|
|
3
|
+
/** Sanity-specific settings forwarded to `provider.agent(threadId, ...)`. */
|
|
4
|
+
type ContentAgentModelSettings = Pick<AgentSettings, 'application' | 'config' | 'format' | 'clientId' | 'onStreamData'>;
|
|
5
|
+
/**
|
|
6
|
+
* Settings accepted by the {@link ContentAgent} constructor — everything
|
|
7
|
+
* `ToolLoopAgent` accepts, minus `model` (resolved from the provider) and
|
|
8
|
+
* plus the Sanity-specific routing fields.
|
|
9
|
+
*/
|
|
10
|
+
type ContentAgentSettings<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet, OUTPUT extends Output.Output = never> = Omit<ToolLoopAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>, 'model'> & ContentAgentModelSettings & {
|
|
11
|
+
/** Provider returned by `createContentAgent` or `createStudioAgent`. */provider: ContentAgentProvider; /** Conversation thread ID. Reusing it continues an existing conversation. */
|
|
12
|
+
threadId: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Sanity Content Agent wrapped as an `ai` SDK agent.
|
|
16
|
+
*
|
|
17
|
+
* Extends {@link ToolLoopAgent} so it inherits `.generate()`, `.stream()`,
|
|
18
|
+
* telemetry, and every other capability of the AI SDK's first-class agent
|
|
19
|
+
* abstraction. Construction is the only thing that differs: pass a `provider`
|
|
20
|
+
* and `threadId` instead of a `model`.
|
|
21
|
+
*
|
|
22
|
+
* In tool-response mode (`config.responseMode: 'tool'`, used by integrations
|
|
23
|
+
* like Linear, Slack, and MS Teams) the agent suppresses raw model text on
|
|
24
|
+
* the wire and delivers all output through a server-side `final_answer`
|
|
25
|
+
* tool. To make that mode safe by default, `ContentAgent`:
|
|
26
|
+
*
|
|
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
|
+
* 2. Composes a `prepareStep` safety net that, on the cap step, forces
|
|
33
|
+
* `toolChoice: { type: 'tool', toolName: 'final_answer' }` when the model
|
|
34
|
+
* has been calling tools but hasn't yet converged.
|
|
35
|
+
*
|
|
36
|
+
* Both defaults are skipped when the consumer passes their own `stopWhen` —
|
|
37
|
+
* they own the loop semantics. Outside tool-response mode the agent is a
|
|
38
|
+
* plain `ToolLoopAgent` with no overrides.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* import { createContentAgent } from 'content-agent'
|
|
43
|
+
* import { ContentAgent } from 'content-agent/agent'
|
|
44
|
+
*
|
|
45
|
+
* const provider = createContentAgent({ organizationId, token })
|
|
46
|
+
*
|
|
47
|
+
* const agent = new ContentAgent({
|
|
48
|
+
* provider,
|
|
49
|
+
* threadId: 'my-thread',
|
|
50
|
+
* tools: { update_issue_status, get_settings },
|
|
51
|
+
* config: { responseMode: 'tool' },
|
|
52
|
+
* })
|
|
53
|
+
*
|
|
54
|
+
* const result = await agent.stream({ messages })
|
|
55
|
+
* for await (const chunk of result.textStream) process.stdout.write(chunk)
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @category Agent
|
|
59
|
+
*/
|
|
60
|
+
declare class ContentAgent<CALL_OPTIONS = never, TOOLS extends ToolSet = ToolSet, OUTPUT extends Output.Output = never> extends ToolLoopAgent<CALL_OPTIONS, TOOLS, OUTPUT> {
|
|
61
|
+
constructor(settings: ContentAgentSettings<CALL_OPTIONS, TOOLS, OUTPUT>);
|
|
62
|
+
}
|
|
63
|
+
export { ContentAgent, ContentAgentSettings };
|
|
64
|
+
//# sourceMappingURL=content-agent.d.ts.map
|
|
@@ -0,0 +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;;;;;;;;;;;;;;;;;;;;;;;;;;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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ToolLoopAgent, stepCountIs } from "ai";
|
|
2
|
+
import { pickBy, isNotNil } from "es-toolkit";
|
|
3
|
+
const DEFAULT_MAX_STEPS = 20, FINAL_ANSWER_TOOL_NAME = "final_answer";
|
|
4
|
+
function isToolResponseMode(settings) {
|
|
5
|
+
return settings.config?.responseMode === "tool";
|
|
6
|
+
}
|
|
7
|
+
function hasFinalAnswer() {
|
|
8
|
+
return ({ steps }) => {
|
|
9
|
+
const lastStep = steps.at(-1);
|
|
10
|
+
return lastStep?.toolCalls?.length ? lastStep.toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME) : !1;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function defaultToolResponseStopWhen() {
|
|
14
|
+
return [hasFinalAnswer(), stepCountIs(DEFAULT_MAX_STEPS)];
|
|
15
|
+
}
|
|
16
|
+
const FORCE_FINAL_ANSWER = {
|
|
17
|
+
type: "tool",
|
|
18
|
+
toolName: FINAL_ANSWER_TOOL_NAME
|
|
19
|
+
};
|
|
20
|
+
function buildSafetyNetPrepareStep(maxSteps, userPrepareStep) {
|
|
21
|
+
return async (options) => {
|
|
22
|
+
const userResult = await userPrepareStep?.(options);
|
|
23
|
+
if (options.stepNumber !== maxSteps - 1) return userResult;
|
|
24
|
+
const toolCalls = options.steps.flatMap((step) => step.toolCalls ?? []);
|
|
25
|
+
return toolCalls.length === 0 || toolCalls.some((call) => call.toolName === FINAL_ANSWER_TOOL_NAME) || options.messages.at(-1)?.role === "assistant" ? userResult : { ...userResult, toolChoice: FORCE_FINAL_ANSWER };
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
class ContentAgent extends ToolLoopAgent {
|
|
29
|
+
constructor(settings) {
|
|
30
|
+
const {
|
|
31
|
+
provider,
|
|
32
|
+
threadId,
|
|
33
|
+
application,
|
|
34
|
+
config,
|
|
35
|
+
format,
|
|
36
|
+
clientId,
|
|
37
|
+
onStreamData,
|
|
38
|
+
stopWhen,
|
|
39
|
+
prepareStep,
|
|
40
|
+
...toolLoopSettings
|
|
41
|
+
} = settings, modelSettings = pickBy(
|
|
42
|
+
{ application, config, format, clientId, onStreamData },
|
|
43
|
+
isNotNil
|
|
44
|
+
), model = provider.agent(threadId, modelSettings), useDefaults = stopWhen === void 0 && isToolResponseMode(modelSettings), resolvedStopWhen = useDefaults ? defaultToolResponseStopWhen() : stopWhen, resolvedPrepareStep = useDefaults ? buildSafetyNetPrepareStep(DEFAULT_MAX_STEPS, prepareStep) : prepareStep;
|
|
45
|
+
super({
|
|
46
|
+
...toolLoopSettings,
|
|
47
|
+
model,
|
|
48
|
+
...resolvedStopWhen !== void 0 && { stopWhen: resolvedStopWhen },
|
|
49
|
+
...resolvedPrepareStep !== void 0 && { prepareStep: resolvedPrepareStep }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
ContentAgent
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=content-agent.js.map
|
|
@@ -0,0 +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 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;"}
|