@ziggs-ai/agent-sdk 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/AgentHost.d.ts +108 -6
- package/dist/AgentHost.js +290 -18
- package/dist/adapters/McpAdapter.js +10 -12
- package/dist/agent/ClaudeSdkAgent.d.ts +9 -0
- package/dist/agent/ClaudeSdkAgent.js +127 -26
- package/dist/agent/claudeSdkProtocol.d.ts +1 -1
- package/dist/agent/claudeSdkProtocol.js +3 -2
- package/dist/agent/claudeSdkTools.js +71 -16
- package/dist/defineAgent.js +31 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +11 -1
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +2 -2
- package/dist/pricing/fleetTierByAgentId.gen.js +9 -2
- package/dist/runtime/PromptBuilder.js +13 -1
- package/dist/server/ConnectionPool.d.ts +19 -0
- package/dist/server/ConnectionPool.js +30 -0
- package/dist/server/OutboxBuffer.d.ts +1 -1
- package/dist/server/ZiggsEffectHandler.d.ts +1 -1
- package/dist/server/ZiggsEffectHandler.js +16 -3
- package/dist/server/agreements/AgreementService.d.ts +8 -3
- package/dist/server/agreements/AgreementService.js +10 -5
- package/dist/server/inboxLoop.d.ts +6 -6
- package/dist/server/inboxLoop.js +39 -21
- package/dist/server/polls/PollRunner.d.ts +36 -1
- package/dist/server/polls/PollRunner.js +42 -2
- package/dist/server/requestMatch.d.ts +5 -0
- package/dist/server/{questMatch.js → requestMatch.js} +2 -2
- package/dist/server/runLauncher.js +24 -3
- package/dist/server/tasks/TaskService.d.ts +5 -5
- package/dist/server/tools/index.d.ts +1 -1
- package/dist/server/tools/index.js +1 -1
- package/dist/server/tools/tier1/protocolRunner.js +2 -2
- package/dist/server/tools/tier1/protocolTools.js +7 -7
- package/dist/server/tools/tier2/marketplaceTools.js +2 -2
- package/dist/server/tools/tier2/mcpConnectionTools.d.ts +33 -0
- package/dist/server/tools/tier2/mcpConnectionTools.js +139 -7
- package/dist/server/tools/tier2/paymentTools.d.ts +0 -8
- package/dist/server/tools/tier2/paymentTools.js +8 -12
- package/dist/tools/ToolManager.d.ts +1 -1
- package/dist/tools/ToolManager.js +2 -1
- package/dist/tools/defineTool.d.ts +7 -0
- package/dist/tools/defineTool.js +12 -0
- package/dist/tools/needs.d.ts +54 -0
- package/dist/tools/needs.js +84 -0
- package/dist/types.d.ts +34 -5
- package/package.json +2 -2
- package/dist/server/questMatch.d.ts +0 -5
|
@@ -63,7 +63,7 @@ cachedAgreements) {
|
|
|
63
63
|
return null;
|
|
64
64
|
return agreementService.respond(payload.agreementId, payload.action);
|
|
65
65
|
case 'agreement-claim': {
|
|
66
|
-
// the one claim verb —
|
|
66
|
+
// the one claim verb — request, standing offer, or link invite.
|
|
67
67
|
if (!payload.agreementId)
|
|
68
68
|
throw new Error('agreement-claim requires `agreementId`');
|
|
69
69
|
return await agreementService.claim(payload.agreementId);
|
|
@@ -240,7 +240,7 @@ async function proposeAgreement(services, payload, chatId, _agents, actingAgentI
|
|
|
240
240
|
audience: payload.proposedTo === 'org' ? 'org' : 'everyone',
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
|
-
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer) or omitted (a
|
|
243
|
+
throw new Error('On a broadcast, providerId must be your own agent id (a standing offer) or omitted (a request — the claiming agent fills the open side).');
|
|
244
244
|
}
|
|
245
245
|
const priced = applyFleetPricingForProtocol('agreement-propose', payload, actingAgentId);
|
|
246
246
|
const { operation: _op, ...fields } = priced;
|
|
@@ -43,10 +43,10 @@ function handlerFor(toolName) {
|
|
|
43
43
|
return result;
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: '
|
|
47
|
-
export const agreementSubcontractTool = defineTool('agreement_subcontract', { description: { type: 'string', required: true }, executorId: { type: 'string', required: true }, parentAgreementId: { type: 'string', required: true }, payerId: 'string', price: { type: 'number', description: '
|
|
48
|
-
export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (
|
|
49
|
-
export const agreementClaimTool = defineTool('agreement_claim', { agreementId: { type: 'string', required: true, description: 'The open agreement to claim (
|
|
46
|
+
export const agreementProposeTool = defineTool('agreement_propose', { description: { type: 'string', required: true }, proposedTo: { type: 'string', required: true }, engagementKind: { type: 'string', enum: ['hire', 'service'], description: 'Contract type. Defaults to service. For a bilateral trust link, use link_propose.' }, parentAgreementId: 'string', providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', billing: { type: 'string', enum: ['total', 'per_task'], description: "How price reads: 'total' (default, escrowed and paid at the end) or 'per_task' (a rate settled as each task completes — standing agreements only, and the default for a hire)." }, agreementDescription: 'string' }, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public) or "org" (your org only): no providerId publishes a REQUEST any agent may claim; providerId = your own id publishes a STANDING OFFER (claiming is agreement_claim; browsing is marketplace_view). For a bilateral trust link, use link_propose (or link_create_invite when you lack the agent id). engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
|
|
47
|
+
export const agreementSubcontractTool = defineTool('agreement_subcontract', { description: { type: 'string', required: true }, executorId: { type: 'string', required: true }, parentAgreementId: { type: 'string', required: true }, payerId: 'string', price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, lifecycle: lifecycleParam, expiresAt: 'string', maxExecutions: 'integer', agreementDescription: 'string' }, handlerFor('agreement_subcontract'), { description: 'Delegate work to another agent under an existing parent agreement. Requires parentAgreementId.', isAgreementCreation: true });
|
|
48
|
+
export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (requests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
|
|
49
|
+
export const agreementClaimTool = defineTool('agreement_claim', { agreementId: { type: 'string', required: true, description: 'The open agreement to claim (request / offer / link invite id)' } }, handlerFor('agreement_claim'), { description: 'Claim an open broadcast agreement by id — the DEFAULT way to engage: terms are already posted, consent is the claim, activation is instant, no negotiation turns. Claims a request (you do the work, the publisher pays), a standing offer (you buy, the publisher works), a hand-off (providerPinned: the publisher\'s hired agent works FOR you — you become the customer, and the payer when it is priced), or a link invite (bilateral trust forms immediately). You become the open party and the agreement activates. A hand-off is claimable only after its provider has accepted (409 until then). Find requests/offers with marketplace_view; listings are take-it-or-leave-it — never counter one. Direct proposals are approved with agreement_respond instead, not claimed.' });
|
|
50
50
|
export const taskSetResultTool = defineTool('task_set_result', {
|
|
51
51
|
taskId: { type: 'string', required: true },
|
|
52
52
|
// same param name as MCP ziggs_task_set_result / PATCH /tasks/:id/state.
|
|
@@ -54,10 +54,10 @@ export const taskSetResultTool = defineTool('task_set_result', {
|
|
|
54
54
|
// Same object contract as MCP ({ summary, status, links, … }).
|
|
55
55
|
result: {
|
|
56
56
|
type: 'object',
|
|
57
|
-
description: 'Outcome payload (summary, status, links, …).
|
|
57
|
+
description: 'Outcome payload (summary, status, links, …). This is what the next agent reads when it picks the work up from its own inbox.',
|
|
58
58
|
},
|
|
59
|
-
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result.
|
|
60
|
-
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: '
|
|
59
|
+
}, handlerFor('task_set_result'), { description: 'Transition a task to its terminal state — completed, failed, or cancelled — and record the result. Set the result here as an object ({ summary, status, links }): a task the parties opted into is closed by this call, whatever else you also sent them. Pass state (not status).' });
|
|
60
|
+
export const agreementCounterTool = defineTool('agreement_counter', { agreementId: { type: 'string', required: true }, price: { type: 'number', description: 'Price in POINTS, as an integer of hundredths — 500 means ϟ5.00. Convert both ways: a human asking for "ϟ2 per task" means 200, not 2. ϟ is the points symbol, not a hundredth.' }, agreementDescription: 'string', expiresAt: 'string', lifecycle: lifecycleParam, maxExecutions: 'integer', description: 'string' }, handlerFor('agreement_counter'), { description: 'Counter a pending proposal with revised terms instead of approving or rejecting. Check status first with agreement_get.' });
|
|
61
61
|
export const agreementGetTool = defineTool('agreement_get', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_get'), { description: 'Read the current proposal status for an agreement (pending, approved, rejected, countered, expired).' });
|
|
62
62
|
export const agreementRevokeTool = defineTool('agreement_revoke', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_revoke'), { description: 'Revoke a pending proposal you created so it can no longer be approved. Use when the user has clearly moved on to a different request before responding to the previous proposal card.' });
|
|
63
63
|
export const agreementFulfillTool = defineTool('agreement_fulfill', { agreementId: { type: 'string', required: true } }, handlerFor('agreement_fulfill'), { description: 'END an agreement you provide — permanently. Fulfilling terminates the whole relationship, not one deliverable: every grant it conferred is revoked, its shared space is torn down, and it cannot be reopened. Finished WORK is reported via task_set_result (the task closes; the agreement stays standing for the next task). Only fulfill when the engagement\'s full scope is delivered and nothing more is expected — never a standing hire that just finished a task.' });
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { MARKETPLACE_CAPABILITIES, marketplaceViewCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
|
-
// the marketplace read (
|
|
3
|
+
// the marketplace read (requests AND standing offers),
|
|
4
4
|
// defined once in api-client's shared capability layer. Publishing is NOT
|
|
5
5
|
// here: it is the tier-1 verb agreement_propose with proposedTo
|
|
6
|
-
// "everyone" | "org" (providerId = own id for an offer, omitted for a
|
|
6
|
+
// "everyone" | "org" (providerId = own id for an offer, omitted for a request);
|
|
7
7
|
// claiming is agreement_claim.
|
|
8
8
|
export const marketplaceViewTool = toolFromCapability(marketplaceViewCapability);
|
|
9
9
|
export const MARKETPLACE_TOOLS = toolsFromCapabilities(MARKETPLACE_CAPABILITIES);
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
import { type ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
2
|
export declare const mcpToolsListTool: ToolDefinition;
|
|
3
|
+
/**
|
|
4
|
+
* How much of one MCP tool result a wake will carry.
|
|
5
|
+
*
|
|
6
|
+
* A remote MCP server hands back the upstream API's payload verbatim, and those
|
|
7
|
+
* are sized for programs, not for a context window: GitHub's `list_pull_requests`
|
|
8
|
+
* with no paging argument answered ~150,000 characters. Past the runtime's limit
|
|
9
|
+
* the result stops being a result — the Claude SDK spills an oversized tool
|
|
10
|
+
* result to a file, and a hosted agent has no filesystem tool to read it with, so
|
|
11
|
+
* the wake ends in "the answer was too big and I cannot see it". Same class of
|
|
12
|
+
* problem as an MCP server that ships a 30K-character tool description, one layer
|
|
13
|
+
* later.
|
|
14
|
+
*
|
|
15
|
+
* A bounded answer with a marker beats an unreadable one: the model can act on
|
|
16
|
+
* "truncated, narrow the call", and it cannot act on a file path.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_MCP_RESULT_CHARS = 24000;
|
|
19
|
+
/**
|
|
20
|
+
* Tool schemas are bigger than tool results and are read once per wake, so they
|
|
21
|
+
* get their own, larger ceiling — a schema cut in half is worse than useless,
|
|
22
|
+
* because the half that survives looks complete.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_MCP_TOOLS_LIST_CHARS = 60000;
|
|
25
|
+
/**
|
|
26
|
+
* Bound one MCP result, telling the model plainly what was cut.
|
|
27
|
+
*
|
|
28
|
+
* Text blocks are cut individually rather than the JSON being sliced, because a
|
|
29
|
+
* sliced JSON string is invalid and a model handed invalid JSON reports the
|
|
30
|
+
* server as broken.
|
|
31
|
+
*/
|
|
32
|
+
export declare function boundMcpResult(result: unknown, max?: number): {
|
|
33
|
+
result: unknown;
|
|
34
|
+
truncated?: Record<string, unknown>;
|
|
35
|
+
};
|
|
3
36
|
export declare const mcpToolCallTool: ToolDefinition;
|
|
4
37
|
export declare const MCP_CONNECTION_TOOLS: ToolDefinition[];
|
|
@@ -4,7 +4,7 @@ import { ConnectionsClient } from '@ziggs-ai/api-client';
|
|
|
4
4
|
import { defineTool } from '../../../tools/defineTool.js';
|
|
5
5
|
import { resolveZiggsGatewayTarget } from '../../../adapters/McpAdapter.js';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Calling a tool on a connected MCP server, one shot.
|
|
8
8
|
*
|
|
9
9
|
* `connection_proxy` cannot do this. That path resolves a NAMED connector from
|
|
10
10
|
* `connection.provider` and checks the action against its fixed action list; a
|
|
@@ -89,19 +89,145 @@ export const mcpToolsListTool = defineTool('mcp_tools_list', {
|
|
|
89
89
|
type: 'string',
|
|
90
90
|
description: 'Grant to use. Omit to use the live grant found on that connection.',
|
|
91
91
|
},
|
|
92
|
+
names: {
|
|
93
|
+
type: 'array',
|
|
94
|
+
items: { type: 'string' },
|
|
95
|
+
description: 'Tool names to return the FULL parameters (inputSchema) for. Omit to get the names and descriptions only — a whole server of schemas is tens of thousands of characters and gets cut, which is worse than not asking.',
|
|
96
|
+
},
|
|
92
97
|
}, async (args, ctx) => {
|
|
93
98
|
const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
|
|
99
|
+
/**
|
|
100
|
+
* Names first, schemas on demand — the same two-step this platform's own MCP
|
|
101
|
+
* surface settled on. One Linear server answers 53 tools and 78,000
|
|
102
|
+
* characters of schema; asking for all of it fills a wake with parameters for
|
|
103
|
+
* tools the agent will not call, and the cut lands mid-schema where the half
|
|
104
|
+
* that survives still looks complete.
|
|
105
|
+
*/
|
|
106
|
+
const wanted = args['names']?.filter((n) => typeof n === 'string' && n);
|
|
94
107
|
const tools = await withClient(ctx, t.connectionId, t.grantId, async (c) => {
|
|
95
108
|
const res = await c.listTools();
|
|
96
|
-
|
|
109
|
+
const rows = res.tools ?? [];
|
|
110
|
+
const picked = wanted?.length ? rows.filter((tool) => wanted.includes(tool.name)) : rows;
|
|
111
|
+
return picked.map((tool) => ({
|
|
97
112
|
name: tool.name,
|
|
98
113
|
description: tool.description ?? null,
|
|
114
|
+
/**
|
|
115
|
+
* The parameters, which this used to drop.
|
|
116
|
+
*
|
|
117
|
+
* Without them an agent has to GUESS what a tool takes, and a guessed
|
|
118
|
+
* argument is not an error — most servers ignore what they do not know
|
|
119
|
+
* and answer with an empty list. That empty list then reads as "nothing
|
|
120
|
+
* happened": a weekly digest reported one item shipped for a week with
|
|
121
|
+
* fifty, because `status`, `filter.state.name` and `updatedAfter` were
|
|
122
|
+
* all invented and all silently matched nothing.
|
|
123
|
+
*/
|
|
124
|
+
...(wanted?.length ? { inputSchema: tool.inputSchema ?? null } : {}),
|
|
99
125
|
}));
|
|
100
126
|
});
|
|
101
|
-
|
|
127
|
+
const missing = wanted?.filter((n) => !tools.some((row) => row.name === n)) ?? [];
|
|
128
|
+
const bounded = boundMcpResult({ tools }, MAX_MCP_TOOLS_LIST_CHARS);
|
|
129
|
+
return {
|
|
130
|
+
connectionId: t.connectionId,
|
|
131
|
+
grantId: t.grantId,
|
|
132
|
+
...bounded.result,
|
|
133
|
+
...(bounded.truncated ? { truncated: bounded.truncated } : {}),
|
|
134
|
+
...(missing.length ? { notFound: missing } : {}),
|
|
135
|
+
...(wanted?.length
|
|
136
|
+
? {}
|
|
137
|
+
: {
|
|
138
|
+
note: 'Names and descriptions only. Call again with names:["<tool>"] to get that tool\'s parameters before you pass any arguments — a parameter the server does not declare is usually ignored, and the empty result reads as real emptiness.',
|
|
139
|
+
}),
|
|
140
|
+
};
|
|
102
141
|
}, {
|
|
103
|
-
description: "List the tools a connected MCP server exposes, through the Ziggs gateway — the owner's credential stays server-side. The grant may allow only some
|
|
142
|
+
description: "List the tools a connected MCP server exposes, through the Ziggs gateway. Names and descriptions by default; pass names:[\"<tool>\"] for those tools' full parameters (inputSchema) — the owner's credential stays server-side. Read the schema before calling a tool with arguments: a parameter the server does not declare is usually ignored rather than rejected, so a guessed filter comes back as an empty result that looks like real emptiness. The grant may allow only some tools; a call outside it is refused by the gateway, not by you.",
|
|
104
143
|
});
|
|
144
|
+
/**
|
|
145
|
+
* How much of one MCP tool result a wake will carry.
|
|
146
|
+
*
|
|
147
|
+
* A remote MCP server hands back the upstream API's payload verbatim, and those
|
|
148
|
+
* are sized for programs, not for a context window: GitHub's `list_pull_requests`
|
|
149
|
+
* with no paging argument answered ~150,000 characters. Past the runtime's limit
|
|
150
|
+
* the result stops being a result — the Claude SDK spills an oversized tool
|
|
151
|
+
* result to a file, and a hosted agent has no filesystem tool to read it with, so
|
|
152
|
+
* the wake ends in "the answer was too big and I cannot see it". Same class of
|
|
153
|
+
* problem as an MCP server that ships a 30K-character tool description, one layer
|
|
154
|
+
* later.
|
|
155
|
+
*
|
|
156
|
+
* A bounded answer with a marker beats an unreadable one: the model can act on
|
|
157
|
+
* "truncated, narrow the call", and it cannot act on a file path.
|
|
158
|
+
*/
|
|
159
|
+
export const MAX_MCP_RESULT_CHARS = 24_000;
|
|
160
|
+
/**
|
|
161
|
+
* Tool schemas are bigger than tool results and are read once per wake, so they
|
|
162
|
+
* get their own, larger ceiling — a schema cut in half is worse than useless,
|
|
163
|
+
* because the half that survives looks complete.
|
|
164
|
+
*/
|
|
165
|
+
export const MAX_MCP_TOOLS_LIST_CHARS = 60_000;
|
|
166
|
+
const TRUNCATION_ADVICE = 'Narrow the call instead of repeating it: most servers take paging (perPage/page) ' +
|
|
167
|
+
'or filter arguments, and a named target usually has a narrower tool than a list.';
|
|
168
|
+
/**
|
|
169
|
+
* Bound one MCP result, telling the model plainly what was cut.
|
|
170
|
+
*
|
|
171
|
+
* Text blocks are cut individually rather than the JSON being sliced, because a
|
|
172
|
+
* sliced JSON string is invalid and a model handed invalid JSON reports the
|
|
173
|
+
* server as broken.
|
|
174
|
+
*/
|
|
175
|
+
export function boundMcpResult(result, max = MAX_MCP_RESULT_CHARS) {
|
|
176
|
+
const whole = JSON.stringify(result) ?? '';
|
|
177
|
+
if (whole.length <= max)
|
|
178
|
+
return { result };
|
|
179
|
+
const content = result?.content;
|
|
180
|
+
if (!Array.isArray(content)) {
|
|
181
|
+
return {
|
|
182
|
+
result: { text: whole.slice(0, max) },
|
|
183
|
+
truncated: {
|
|
184
|
+
originalChars: whole.length,
|
|
185
|
+
keptChars: max,
|
|
186
|
+
note: `Result was ${whole.length} characters and is cut to ${max}. ${TRUNCATION_ADVICE}`,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const kept = [];
|
|
191
|
+
let used = 0;
|
|
192
|
+
let dropped = 0;
|
|
193
|
+
for (const block of content) {
|
|
194
|
+
const text = block?.text;
|
|
195
|
+
if (typeof text !== 'string') {
|
|
196
|
+
const size = (JSON.stringify(block) ?? '').length;
|
|
197
|
+
if (used + size > max) {
|
|
198
|
+
dropped += 1;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
used += size;
|
|
202
|
+
kept.push(block);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const room = max - used;
|
|
206
|
+
if (room <= 0) {
|
|
207
|
+
dropped += 1;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (text.length <= room) {
|
|
211
|
+
used += text.length;
|
|
212
|
+
kept.push(block);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
kept.push({
|
|
216
|
+
...block,
|
|
217
|
+
text: `${text.slice(0, room)}\n\n[…cut here: this block was ${text.length} characters]`,
|
|
218
|
+
});
|
|
219
|
+
used = max;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
result: { ...result, content: kept },
|
|
223
|
+
truncated: {
|
|
224
|
+
originalChars: whole.length,
|
|
225
|
+
keptChars: used,
|
|
226
|
+
droppedBlocks: dropped,
|
|
227
|
+
note: `Result was ${whole.length} characters, over the ${max}-character limit, so it is cut. What you have is the beginning, not the whole answer — do not report it as complete. ${TRUNCATION_ADVICE}`,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
105
231
|
export const mcpToolCallTool = defineTool('mcp_tool_call', {
|
|
106
232
|
tool: {
|
|
107
233
|
type: 'string',
|
|
@@ -125,12 +251,18 @@ export const mcpToolCallTool = defineTool('mcp_tool_call', {
|
|
|
125
251
|
if (!name)
|
|
126
252
|
throw new Error('tool is required');
|
|
127
253
|
const t = await resolveTarget(ctx, args['connectionId'], args['grantId']);
|
|
128
|
-
const
|
|
254
|
+
const raw = await withClient(ctx, t.connectionId, t.grantId, (c) => c.callTool({
|
|
129
255
|
name,
|
|
130
256
|
arguments: args['args'] ?? {},
|
|
131
257
|
}));
|
|
132
|
-
|
|
258
|
+
const bounded = boundMcpResult(raw);
|
|
259
|
+
return {
|
|
260
|
+
connectionId: t.connectionId,
|
|
261
|
+
tool: name,
|
|
262
|
+
result: bounded.result,
|
|
263
|
+
...(bounded.truncated ? { truncated: bounded.truncated } : {}),
|
|
264
|
+
};
|
|
133
265
|
}, {
|
|
134
|
-
description: "Call one tool on a connected MCP server through the Ziggs gateway. Use this for a connection whose provider is a remote MCP server — connection_proxy is for named connectors (github, jira) and refuses these with \"Unknown provider: mcp\". The gateway injects the owner's token upstream and checks the tool name against your grant, so you never hold the credential. Discover names with mcp_tools_list, or read allowed_actions off the grant in grant_list.",
|
|
266
|
+
description: "Call one tool on a connected MCP server through the Ziggs gateway. Use this for a connection whose provider is a remote MCP server — connection_proxy is for named connectors (github, jira) and refuses these with \"Unknown provider: mcp\". The gateway injects the owner's token upstream and checks the tool name against your grant, so you never hold the credential. Discover names with mcp_tools_list, or read allowed_actions off the grant in grant_list. Results are bounded: a payload over the limit comes back cut, with a `truncated` field saying so — pass the server's paging or filter arguments rather than repeating the call.",
|
|
135
267
|
});
|
|
136
268
|
export const MCP_CONNECTION_TOOLS = [mcpToolsListTool, mcpToolCallTool];
|
|
@@ -1,11 +1,3 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../../../tools/defineTool.js';
|
|
2
2
|
export declare const paymentBalanceTool: ToolDefinition;
|
|
3
|
-
export declare const paymentTransferTool: ToolDefinition;
|
|
4
|
-
export declare const paymentWaitForApprovalTool: ToolDefinition;
|
|
5
|
-
export declare const paymentHoldTool: ToolDefinition;
|
|
6
|
-
export declare const paymentReleaseTool: ToolDefinition;
|
|
7
|
-
export declare const paymentResolveWalletTool: ToolDefinition;
|
|
8
|
-
export declare const paymentIssueGrantTool: ToolDefinition;
|
|
9
|
-
export declare const paymentAttenuateGrantTool: ToolDefinition;
|
|
10
|
-
export declare const paymentRevokeGrantTool: ToolDefinition;
|
|
11
3
|
export declare const PAYMENT_TOOLS: ToolDefinition[];
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import { PAYMENT_CAPABILITIES, paymentBalanceCapability
|
|
1
|
+
import { PAYMENT_CAPABILITIES, paymentBalanceCapability } from '@ziggs-ai/api-client';
|
|
2
2
|
import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabilityAdapter.js';
|
|
3
3
|
// the wallet toolset is defined once in api-client's shared
|
|
4
|
-
// capability layer; this file only adapts it onto the SDK surface.
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// capability layer; this file only adapts it onto the SDK surface.
|
|
5
|
+
//
|
|
6
|
+
// The movement verbs are gone from both agent surfaces —
|
|
7
|
+
// transfer, hold, release, wait_for_approval, resolve_wallet, and the three
|
|
8
|
+
// grant mutations. Points are a meter, and an agent does not move the meter on
|
|
9
|
+
// its own. Agreements still settle in points server-side, so nothing an agent
|
|
10
|
+
// needs in order to be paid went with them. Reading your own balance survives.
|
|
7
11
|
export const paymentBalanceTool = toolFromCapability(paymentBalanceCapability);
|
|
8
|
-
export const paymentTransferTool = toolFromCapability(paymentTransferCapability);
|
|
9
|
-
export const paymentWaitForApprovalTool = toolFromCapability(paymentWaitForApprovalCapability);
|
|
10
|
-
export const paymentHoldTool = toolFromCapability(paymentHoldCapability);
|
|
11
|
-
export const paymentReleaseTool = toolFromCapability(paymentReleaseCapability);
|
|
12
|
-
export const paymentResolveWalletTool = toolFromCapability(paymentResolveWalletCapability);
|
|
13
|
-
export const paymentIssueGrantTool = toolFromCapability(paymentIssueGrantCapability);
|
|
14
|
-
export const paymentAttenuateGrantTool = toolFromCapability(paymentAttenuateGrantCapability);
|
|
15
|
-
export const paymentRevokeGrantTool = toolFromCapability(paymentRevokeGrantCapability);
|
|
16
12
|
export const PAYMENT_TOOLS = toolsFromCapabilities(PAYMENT_CAPABILITIES);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import AjvModule from 'ajv';
|
|
2
2
|
import { ToolProvider } from './ToolProvider.js';
|
|
3
|
+
import { toolName } from './defineTool.js';
|
|
3
4
|
const AjvClass = AjvModule.default ?? AjvModule;
|
|
4
5
|
const ajv = new AjvClass({ allErrors: true, strict: false });
|
|
5
6
|
function makeError(message, type, extra) {
|
|
@@ -49,7 +50,7 @@ export class ToolManager extends ToolProvider {
|
|
|
49
50
|
throw makeError('Tool must have a schema', 'invalid_tool');
|
|
50
51
|
if (typeof tool.handler !== 'function')
|
|
51
52
|
throw makeError('Tool must have a handler function', 'invalid_tool');
|
|
52
|
-
const name = (tool
|
|
53
|
+
const name = toolName(tool);
|
|
53
54
|
if (!name)
|
|
54
55
|
throw makeError('Tool schema must have a name', 'invalid_tool');
|
|
55
56
|
this.tools.set(name, tool);
|
|
@@ -24,4 +24,11 @@ export interface DefineToolOptions {
|
|
|
24
24
|
isAgreementCreation?: boolean;
|
|
25
25
|
isGenericFallback?: boolean;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The name a tool registers under. `defineTool` always writes
|
|
29
|
+
* `schema.function.name`, but hand-built and capability-adapted schemas have put
|
|
30
|
+
* it at the top level too, so both are read — in one place, rather than at every
|
|
31
|
+
* call site that needs to identify a tool.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toolName(tool: ToolDefinition | undefined): string | undefined;
|
|
27
34
|
export declare function defineTool(name: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>, context: Record<string, unknown>) => Promise<unknown>, options?: DefineToolOptions): ToolDefinition;
|
package/dist/tools/defineTool.js
CHANGED
|
@@ -43,6 +43,18 @@ function convertObject(fields) {
|
|
|
43
43
|
result['required'] = required;
|
|
44
44
|
return result;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The name a tool registers under. `defineTool` always writes
|
|
48
|
+
* `schema.function.name`, but hand-built and capability-adapted schemas have put
|
|
49
|
+
* it at the top level too, so both are read — in one place, rather than at every
|
|
50
|
+
* call site that needs to identify a tool.
|
|
51
|
+
*/
|
|
52
|
+
export function toolName(tool) {
|
|
53
|
+
const fromFunction = tool?.schema?.function?.['name'];
|
|
54
|
+
const fromTop = tool?.schema?.['name'];
|
|
55
|
+
const name = fromFunction ?? fromTop;
|
|
56
|
+
return typeof name === 'string' && name ? name : undefined;
|
|
57
|
+
}
|
|
46
58
|
export function defineTool(name, params, handler, options = {}) {
|
|
47
59
|
if (!name || typeof name !== 'string')
|
|
48
60
|
throw new Error('defineTool: name is required');
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* needs.ts — what an agent cannot work without, declared once.
|
|
3
|
+
*
|
|
4
|
+
* An agent is otherwise two hand-written lists — instructions and tools — with
|
|
5
|
+
* nothing checking they agree, so a prompt can promise work the toolset has no
|
|
6
|
+
* way to do and the mismatch only surfaces on a real hire. A third list to
|
|
7
|
+
* compare against the other two would drift the same way, so the declaration
|
|
8
|
+
* DERIVES the tools instead. The author never writes that part of the list, so
|
|
9
|
+
* the mismatch cannot be written.
|
|
10
|
+
*
|
|
11
|
+
* Scope is agents built on this SDK.
|
|
12
|
+
*/
|
|
13
|
+
import { type ToolDefinition } from './defineTool.js';
|
|
14
|
+
/**
|
|
15
|
+
* A remote MCP server is stored as `provider: 'mcp'` and goes through the
|
|
16
|
+
* gateway. `connection_proxy` resolves a NAMED connector and refuses it with
|
|
17
|
+
* "Unknown provider: mcp" — the second wall linear-janitor hit.
|
|
18
|
+
*/
|
|
19
|
+
export declare const MCP_PROVIDER = "mcp";
|
|
20
|
+
/**
|
|
21
|
+
* One shape for now, the one we have actually hit. A second kind means a case in
|
|
22
|
+
* {@link toolsForNeed}: a need nothing derives tools from is a need nothing
|
|
23
|
+
* enforces.
|
|
24
|
+
*/
|
|
25
|
+
export interface ConnectionNeed {
|
|
26
|
+
/** Provider, e.g. 'mcp' or a named connector like 'github'. */
|
|
27
|
+
connection: string;
|
|
28
|
+
}
|
|
29
|
+
export type AgentNeed = ConnectionNeed;
|
|
30
|
+
/** Reads a manifest's `needs` field defensively — it crosses a package edge. */
|
|
31
|
+
export declare function normalizeNeeds(input: unknown): AgentNeed[];
|
|
32
|
+
/**
|
|
33
|
+
* The tools a stated need implies. `connection_list_grants` rides every need
|
|
34
|
+
* because seeing the grant is half of using it: the janitor's first failure was
|
|
35
|
+
* an agent that held a Linear grant and had no way to notice.
|
|
36
|
+
*/
|
|
37
|
+
export declare function toolsForNeed(need: AgentNeed): ToolDefinition[];
|
|
38
|
+
/** Deduped by tool name, so overlapping needs (and a hand-listed tool) merge. */
|
|
39
|
+
export declare function toolsForNeeds(needs: AgentNeed[]): ToolDefinition[];
|
|
40
|
+
/** One line naming what is missing, for a refusal a human can act on. */
|
|
41
|
+
export declare function describeNeed(need: AgentNeed): string;
|
|
42
|
+
/**
|
|
43
|
+
* Which declared needs have no live grant behind them.
|
|
44
|
+
*
|
|
45
|
+
* `providers` are the providers of the agent's live connection grants. The label
|
|
46
|
+
* a grant carries is the provider, sometimes suffixed with the owner's account
|
|
47
|
+
* ("github — acme/infra"), so the provider is the part before the separator.
|
|
48
|
+
*/
|
|
49
|
+
export declare function unmetNeeds(needs: AgentNeed[], providers: Array<string | null | undefined>): AgentNeed[];
|
|
50
|
+
/**
|
|
51
|
+
* The refusal itself. Names the missing access and who can fix it, because the
|
|
52
|
+
* agent cannot: issuing a connection grant is the connection owner's act.
|
|
53
|
+
*/
|
|
54
|
+
export declare function unmetNeedsMessage(unmet: AgentNeed[]): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* needs.ts — what an agent cannot work without, declared once.
|
|
3
|
+
*
|
|
4
|
+
* An agent is otherwise two hand-written lists — instructions and tools — with
|
|
5
|
+
* nothing checking they agree, so a prompt can promise work the toolset has no
|
|
6
|
+
* way to do and the mismatch only surfaces on a real hire. A third list to
|
|
7
|
+
* compare against the other two would drift the same way, so the declaration
|
|
8
|
+
* DERIVES the tools instead. The author never writes that part of the list, so
|
|
9
|
+
* the mismatch cannot be written.
|
|
10
|
+
*
|
|
11
|
+
* Scope is agents built on this SDK.
|
|
12
|
+
*/
|
|
13
|
+
import { toolName } from './defineTool.js';
|
|
14
|
+
import { connectionListGrantsTool, connectionProxyTool } from '../server/tools/tier2/connectionTools.js';
|
|
15
|
+
import { MCP_CONNECTION_TOOLS } from '../server/tools/tier2/mcpConnectionTools.js';
|
|
16
|
+
/**
|
|
17
|
+
* A remote MCP server is stored as `provider: 'mcp'` and goes through the
|
|
18
|
+
* gateway. `connection_proxy` resolves a NAMED connector and refuses it with
|
|
19
|
+
* "Unknown provider: mcp" — the second wall linear-janitor hit.
|
|
20
|
+
*/
|
|
21
|
+
export const MCP_PROVIDER = 'mcp';
|
|
22
|
+
/** Reads a manifest's `needs` field defensively — it crosses a package edge. */
|
|
23
|
+
export function normalizeNeeds(input) {
|
|
24
|
+
if (!Array.isArray(input))
|
|
25
|
+
return [];
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const raw of input) {
|
|
28
|
+
const connection = raw?.connection;
|
|
29
|
+
if (typeof connection === 'string' && connection.trim()) {
|
|
30
|
+
out.push({ connection: connection.trim() });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The tools a stated need implies. `connection_list_grants` rides every need
|
|
37
|
+
* because seeing the grant is half of using it: the janitor's first failure was
|
|
38
|
+
* an agent that held a Linear grant and had no way to notice.
|
|
39
|
+
*/
|
|
40
|
+
export function toolsForNeed(need) {
|
|
41
|
+
return need.connection === MCP_PROVIDER
|
|
42
|
+
? [connectionListGrantsTool, ...MCP_CONNECTION_TOOLS]
|
|
43
|
+
: [connectionListGrantsTool, connectionProxyTool];
|
|
44
|
+
}
|
|
45
|
+
/** Deduped by tool name, so overlapping needs (and a hand-listed tool) merge. */
|
|
46
|
+
export function toolsForNeeds(needs) {
|
|
47
|
+
const byName = new Map();
|
|
48
|
+
for (const need of needs) {
|
|
49
|
+
for (const tool of toolsForNeed(need)) {
|
|
50
|
+
const name = toolName(tool);
|
|
51
|
+
if (name)
|
|
52
|
+
byName.set(name, tool);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...byName.values()];
|
|
56
|
+
}
|
|
57
|
+
/** One line naming what is missing, for a refusal a human can act on. */
|
|
58
|
+
export function describeNeed(need) {
|
|
59
|
+
return need.connection === MCP_PROVIDER
|
|
60
|
+
? 'a connection to an MCP server'
|
|
61
|
+
: `a ${need.connection} connection`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Which declared needs have no live grant behind them.
|
|
65
|
+
*
|
|
66
|
+
* `providers` are the providers of the agent's live connection grants. The label
|
|
67
|
+
* a grant carries is the provider, sometimes suffixed with the owner's account
|
|
68
|
+
* ("github — acme/infra"), so the provider is the part before the separator.
|
|
69
|
+
*/
|
|
70
|
+
export function unmetNeeds(needs, providers) {
|
|
71
|
+
const held = new Set(providers
|
|
72
|
+
.map((p) => String(p ?? '').split('—')[0].trim().toLowerCase())
|
|
73
|
+
.filter(Boolean));
|
|
74
|
+
return needs.filter((n) => !held.has(n.connection.toLowerCase()));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The refusal itself. Names the missing access and who can fix it, because the
|
|
78
|
+
* agent cannot: issuing a connection grant is the connection owner's act.
|
|
79
|
+
*/
|
|
80
|
+
export function unmetNeedsMessage(unmet) {
|
|
81
|
+
const list = unmet.map(describeNeed).join(', ');
|
|
82
|
+
return (`Cannot start this task: no live grant for ${list}. ` +
|
|
83
|
+
'Ask the connection owner to issue one for this agent, then re-assign the task.');
|
|
84
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* One outcome vocabulary, one ctx shape, one transition signature.
|
|
5
5
|
* Every state — parked or thinking — re-enters with exactly one Outcome.
|
|
6
6
|
*/
|
|
7
|
+
import type { AgentNeed } from './tools/needs.js';
|
|
7
8
|
export interface AgreementRef {
|
|
8
9
|
agreementId: string;
|
|
9
10
|
parties?: {
|
|
@@ -202,7 +203,7 @@ export interface Ctx {
|
|
|
202
203
|
* Recent wake timestamps (epoch ms) for this lane (claude-sdk cognition
|
|
203
204
|
* only). Backstop circuit breaker: if a lane wakes faster than any real
|
|
204
205
|
* traffic could drive it, the brain skips the LLM instead of burning the
|
|
205
|
-
*
|
|
206
|
+
* key on a wake storm.
|
|
206
207
|
*/
|
|
207
208
|
recentDeliveries?: number[];
|
|
208
209
|
}
|
|
@@ -327,11 +328,23 @@ export interface LlmResponse {
|
|
|
327
328
|
completionTokens?: number;
|
|
328
329
|
};
|
|
329
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* One participant on the snapshot roster. `kind` is the declared holder kind the
|
|
333
|
+
* server projection stamped, so a consumer reads what a participant IS instead
|
|
334
|
+
* of inferring it from which array the row arrived in. Optional because a
|
|
335
|
+
* backend that predates it simply omits the field.
|
|
336
|
+
*/
|
|
337
|
+
export interface SnapshotParticipant {
|
|
338
|
+
role?: string;
|
|
339
|
+
kind?: string;
|
|
340
|
+
isYou?: boolean;
|
|
341
|
+
[key: string]: unknown;
|
|
342
|
+
}
|
|
330
343
|
export interface ContextSnapshot {
|
|
331
344
|
history: HistoryEntry[];
|
|
332
345
|
agreements: unknown[];
|
|
333
|
-
agents:
|
|
334
|
-
users:
|
|
346
|
+
agents: SnapshotParticipant[];
|
|
347
|
+
users: SnapshotParticipant[];
|
|
335
348
|
timelineSummary?: string | null;
|
|
336
349
|
latestSequence?: string | null;
|
|
337
350
|
unavailable?: boolean;
|
|
@@ -471,10 +484,17 @@ export type Effect = {
|
|
|
471
484
|
stateId?: string;
|
|
472
485
|
/** Replay-stable write keys for this wake — write-capable tools derive their idempotency key from here, never from the LLM. */
|
|
473
486
|
writeKeys?: DeliveryWriteKeys;
|
|
474
|
-
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* `receiverId` is optional: in a room with one other participant the backend
|
|
490
|
+
* resolves the recipient from the room itself, which is the only answer
|
|
491
|
+
* available when the wake's sender belongs to a DIFFERENT room.
|
|
492
|
+
* Name a receiver whenever the room holds more than two parties.
|
|
493
|
+
*/
|
|
494
|
+
| {
|
|
475
495
|
kind: 'send-message';
|
|
476
496
|
sessionId: string;
|
|
477
|
-
receiverId
|
|
497
|
+
receiverId?: string;
|
|
478
498
|
text: string;
|
|
479
499
|
messageId?: string;
|
|
480
500
|
} | {
|
|
@@ -534,6 +554,13 @@ export interface DefineAgentInput {
|
|
|
534
554
|
initial: string;
|
|
535
555
|
states: Record<string, State>;
|
|
536
556
|
tools?: unknown[];
|
|
557
|
+
/**
|
|
558
|
+
* Access this agent cannot work without. Declaring a need PRODUCES the tools
|
|
559
|
+
* for it — do not also hand-list them — and makes work that arrives without
|
|
560
|
+
* the grant fail at task start with the missing thing named, instead of
|
|
561
|
+
* part-way through a real hire.
|
|
562
|
+
*/
|
|
563
|
+
needs?: AgentNeed[];
|
|
537
564
|
services?: ServicesConfig;
|
|
538
565
|
operatorKey?: string;
|
|
539
566
|
taskTools?: 'all' | 'none' | string[];
|
|
@@ -557,6 +584,8 @@ export interface AgentConfig {
|
|
|
557
584
|
systemPrompt?: string;
|
|
558
585
|
specialization?: string;
|
|
559
586
|
tools: unknown[];
|
|
587
|
+
/** Normalized declared needs — see DefineAgentInput.needs. */
|
|
588
|
+
needs?: AgentNeed[];
|
|
560
589
|
model: string;
|
|
561
590
|
workflow: Workflow;
|
|
562
591
|
services?: ServicesConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@anthropic-ai/claude-agent-sdk": "^0.3.204",
|
|
27
27
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
28
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
29
|
-
"@ziggs-ai/api-client": "0.
|
|
29
|
+
"@ziggs-ai/api-client": "0.11.0",
|
|
30
30
|
"ajv": "^8.17.1",
|
|
31
31
|
"dotenv": "^17.2.3",
|
|
32
32
|
"openai": "^4.0.0",
|