@ziggs-ai/agent-sdk 0.11.0 → 0.11.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.
@@ -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.schema.function?.['name'] || tool.schema['name']);
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;
@@ -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
- * fleet key on a wake storm.
206
+ * key on a wake storm.
206
207
  */
207
208
  recentDeliveries?: number[];
208
209
  }
@@ -534,6 +535,13 @@ export interface DefineAgentInput {
534
535
  initial: string;
535
536
  states: Record<string, State>;
536
537
  tools?: unknown[];
538
+ /**
539
+ * Access this agent cannot work without. Declaring a need PRODUCES the tools
540
+ * for it — do not also hand-list them — and makes work that arrives without
541
+ * the grant fail at task start with the missing thing named, instead of
542
+ * part-way through a real hire.
543
+ */
544
+ needs?: AgentNeed[];
537
545
  services?: ServicesConfig;
538
546
  operatorKey?: string;
539
547
  taskTools?: 'all' | 'none' | string[];
@@ -557,6 +565,8 @@ export interface AgentConfig {
557
565
  systemPrompt?: string;
558
566
  specialization?: string;
559
567
  tools: unknown[];
568
+ /** Normalized declared needs — see DefineAgentInput.needs. */
569
+ needs?: AgentNeed[];
560
570
  model: string;
561
571
  workflow: Workflow;
562
572
  services?: ServicesConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ziggs-ai/agent-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
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.10.0",
29
+ "@ziggs-ai/api-client": "0.10.4",
30
30
  "ajv": "^8.17.1",
31
31
  "dotenv": "^17.2.3",
32
32
  "openai": "^4.0.0",