@irogane/kaji 0.2.0-beta.11

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.
Files changed (136) hide show
  1. package/LICENSE +105 -0
  2. package/README.md +598 -0
  3. package/contracts/README.md +14 -0
  4. package/contracts/beta-core-v1.json +52 -0
  5. package/contracts/cli/init-cases-v1.json +27 -0
  6. package/contracts/errors/error-codes.json +48 -0
  7. package/contracts/errors/integration-recovery-v1.json +127 -0
  8. package/contracts/errors/provider-normalization.json +111 -0
  9. package/contracts/events/conformance-invalid.json +20 -0
  10. package/contracts/events/conformance.json +511 -0
  11. package/contracts/events/new-kaji-event-v1.schema.json +1021 -0
  12. package/contracts/events/stored-kaji-event-v1.schema.json +1025 -0
  13. package/contracts/feature-tiers-v1.json +488 -0
  14. package/contracts/integrations/abi-index-v1.json +8 -0
  15. package/contracts/integrations/conformance-invalid.json +443 -0
  16. package/contracts/integrations/conformance-valid.json +121 -0
  17. package/contracts/integrations/copy-provenance-v1.schema.json +61 -0
  18. package/contracts/integrations/echo-tool-abi-v1.json +37 -0
  19. package/contracts/integrations/github-api-conformance-v1.json +644 -0
  20. package/contracts/integrations/github-tool-abi-typescript-v1.json +369 -0
  21. package/contracts/integrations/github-tool-abi-v1.json +146 -0
  22. package/contracts/integrations/gmail-api-conformance-v1.json +750 -0
  23. package/contracts/integrations/gmail-tool-abi-v1.json +62 -0
  24. package/contracts/integrations/index.schema.json +37 -0
  25. package/contracts/integrations/manifest.schema.json +119 -0
  26. package/contracts/parity/expected-normalized.json +4906 -0
  27. package/contracts/parity/scenarios.json +100 -0
  28. package/contracts/parity/scenarios.schema.json +214 -0
  29. package/contracts/providers/cost-conformance.json +112 -0
  30. package/contracts/release/github-proof-v1.schema.json +138 -0
  31. package/contracts/release/gmail-proof-v1.schema.json +138 -0
  32. package/contracts/release/kaji-ts-consumer-handoff-v1.schema.json +1289 -0
  33. package/contracts/release/publisher-identity-receipt-v1.schema.json +319 -0
  34. package/contracts/release/typescript-onboarding-evidence-v1.schema.json +740 -0
  35. package/contracts/tools/conformance-invalid.json +271 -0
  36. package/contracts/tools/conformance-valid.json +78 -0
  37. package/contracts/tools/tool-schema-v1.schema.json +18 -0
  38. package/dist/anthropic.cjs +1231 -0
  39. package/dist/anthropic.cjs.map +1 -0
  40. package/dist/anthropic.d.cts +26 -0
  41. package/dist/anthropic.d.ts +26 -0
  42. package/dist/anthropic.js +270 -0
  43. package/dist/anthropic.js.map +1 -0
  44. package/dist/auth.cjs +1507 -0
  45. package/dist/auth.cjs.map +1 -0
  46. package/dist/auth.d.cts +129 -0
  47. package/dist/auth.d.ts +129 -0
  48. package/dist/auth.js +1039 -0
  49. package/dist/auth.js.map +1 -0
  50. package/dist/base-B9FRMcP8.d.cts +140 -0
  51. package/dist/base-nHQd1VtS.d.ts +140 -0
  52. package/dist/chunk-AAM33KAO.js +4367 -0
  53. package/dist/chunk-AAM33KAO.js.map +1 -0
  54. package/dist/chunk-KAJ6BM64.js +153 -0
  55. package/dist/chunk-KAJ6BM64.js.map +1 -0
  56. package/dist/chunk-KCAXIOZS.js +308 -0
  57. package/dist/chunk-KCAXIOZS.js.map +1 -0
  58. package/dist/chunk-LSJ4AVO2.js +243 -0
  59. package/dist/chunk-LSJ4AVO2.js.map +1 -0
  60. package/dist/chunk-TM7ZGOJX.js +716 -0
  61. package/dist/chunk-TM7ZGOJX.js.map +1 -0
  62. package/dist/cli/bin.d.ts +2 -0
  63. package/dist/cli/bin.js +13 -0
  64. package/dist/cli/bin.js.map +1 -0
  65. package/dist/cli/chunk-2RCWPRVY.js +6277 -0
  66. package/dist/cli/chunk-2RCWPRVY.js.map +1 -0
  67. package/dist/cli/chunk-SEBX54TR.js +681 -0
  68. package/dist/cli/chunk-SEBX54TR.js.map +1 -0
  69. package/dist/cli/index.d.ts +232 -0
  70. package/dist/cli/index.js +11 -0
  71. package/dist/cli/index.js.map +1 -0
  72. package/dist/cli/init-worker.d.ts +2 -0
  73. package/dist/cli/init-worker.js +18 -0
  74. package/dist/cli/init-worker.js.map +1 -0
  75. package/dist/cli/integration-copy-worker.js +48 -0
  76. package/dist/cli/integration-copy-worker.js.map +1 -0
  77. package/dist/cli/package-entry-cjs.cjs +21 -0
  78. package/dist/cli/package-entry-cjs.cjs.map +1 -0
  79. package/dist/cli/package-entry-cjs.d.cts +2 -0
  80. package/dist/cli/package-entry.d.ts +2 -0
  81. package/dist/cli/package-entry.js +13 -0
  82. package/dist/cli/package-entry.js.map +1 -0
  83. package/dist/context-BaFHrQHv.d.cts +21 -0
  84. package/dist/context-BaFHrQHv.d.ts +21 -0
  85. package/dist/context-C-YPY-GS.d.cts +1538 -0
  86. package/dist/context-C-YPY-GS.d.ts +1538 -0
  87. package/dist/index.cjs +12346 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.cts +1560 -0
  90. package/dist/index.d.ts +1560 -0
  91. package/dist/index.js +7852 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/integrations/github.cjs +2092 -0
  94. package/dist/integrations/github.cjs.map +1 -0
  95. package/dist/integrations/github.d.cts +21 -0
  96. package/dist/integrations/github.d.ts +21 -0
  97. package/dist/integrations/github.js +2088 -0
  98. package/dist/integrations/github.js.map +1 -0
  99. package/dist/integrations.cjs +3370 -0
  100. package/dist/integrations.cjs.map +1 -0
  101. package/dist/integrations.d.cts +202 -0
  102. package/dist/integrations.d.ts +202 -0
  103. package/dist/integrations.js +2650 -0
  104. package/dist/integrations.js.map +1 -0
  105. package/dist/observability-Cj--OkME.d.cts +96 -0
  106. package/dist/observability-Cj--OkME.d.ts +96 -0
  107. package/dist/openai.cjs +1235 -0
  108. package/dist/openai.cjs.map +1 -0
  109. package/dist/openai.d.cts +32 -0
  110. package/dist/openai.d.ts +32 -0
  111. package/dist/openai.js +272 -0
  112. package/dist/openai.js.map +1 -0
  113. package/dist/testing.cjs +554 -0
  114. package/dist/testing.cjs.map +1 -0
  115. package/dist/testing.d.cts +43 -0
  116. package/dist/testing.d.ts +43 -0
  117. package/dist/testing.js +118 -0
  118. package/dist/testing.js.map +1 -0
  119. package/package.json +188 -0
  120. package/registry/echo/index.ts +53 -0
  121. package/registry/echo/manifest.json +53 -0
  122. package/registry/github/LICENSE +105 -0
  123. package/registry/github/client.ts +1727 -0
  124. package/registry/github/index.ts +263 -0
  125. package/registry/github/manifest.json +227 -0
  126. package/registry/github/owner-fixtures.json +10 -0
  127. package/registry/github/tests/github.test.ts +32 -0
  128. package/registry/gmail/LICENSE +105 -0
  129. package/registry/gmail/client.ts +548 -0
  130. package/registry/gmail/index.ts +165 -0
  131. package/registry/gmail/manifest.json +102 -0
  132. package/registry/gmail/owner-fixtures.json +10 -0
  133. package/registry/gmail/tests/gmail.test.ts +32 -0
  134. package/registry/index.json +21 -0
  135. package/registry/index.schema.json +37 -0
  136. package/registry/schema.json +119 -0
@@ -0,0 +1,165 @@
1
+ // This is YOUR Gmail integration. Edit it.
2
+
3
+ import {
4
+ Integration,
5
+ type MetricsSink,
6
+ type ToolExecutionContext,
7
+ type ToolHandler,
8
+ type ToolSpec,
9
+ type TraceSink,
10
+ } from "@irogane/kaji";
11
+ import { createGmailRequester } from "@irogane/kaji/integrations";
12
+
13
+ import { GmailClient } from "./client";
14
+
15
+ export type SharedGmailClient = Pick<GmailClient, "listMessages" | "getMessage" | "sendMessage">;
16
+
17
+ function parameters(
18
+ properties: Readonly<Record<string, unknown>>,
19
+ required: readonly string[],
20
+ ): Record<string, unknown> {
21
+ return {
22
+ $schema: "https://json-schema.org/draft/2020-12/schema",
23
+ type: "object",
24
+ properties,
25
+ required: [...required],
26
+ additionalProperties: false,
27
+ };
28
+ }
29
+
30
+ function specs(): readonly ToolSpec[] {
31
+ return [
32
+ {
33
+ name: "list_messages",
34
+ description:
35
+ "List messages in the authenticated user's mailbox. Pass `page_token` from a prior result's `next_page_token` to page through results.",
36
+ parameters: parameters(
37
+ {
38
+ query: { type: "string", minLength: 1, maxLength: 1_024 },
39
+ max_results: { type: "integer", minimum: 1, maximum: 100, default: 10 },
40
+ page_token: { type: "string", minLength: 1, maxLength: 2_048 },
41
+ },
42
+ [],
43
+ ),
44
+ risk: "read",
45
+ parallel_safe: true,
46
+ timeout_ms: 10_000,
47
+ },
48
+ {
49
+ name: "get_message",
50
+ description: "Get a message from the authenticated user's mailbox.",
51
+ parameters: parameters(
52
+ {
53
+ message_id: { type: "string", minLength: 1, maxLength: 128 },
54
+ },
55
+ ["message_id"],
56
+ ),
57
+ risk: "read",
58
+ parallel_safe: true,
59
+ timeout_ms: 10_000,
60
+ },
61
+ {
62
+ name: "send_message",
63
+ description:
64
+ "Send an email as the authenticated user. `raw` is the complete RFC 2822 message, base64url-encoded.",
65
+ parameters: parameters(
66
+ {
67
+ raw: { type: "string", minLength: 1, maxLength: 1_048_576 },
68
+ },
69
+ ["raw"],
70
+ ),
71
+ risk: "external_effect",
72
+ parallel_safe: false,
73
+ timeout_ms: 15_000,
74
+ },
75
+ ];
76
+ }
77
+
78
+ function objectResult(value: unknown): Record<string, unknown> {
79
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
80
+ throw new Error("Gmail client returned an invalid tool result");
81
+ }
82
+ return value as Record<string, unknown>;
83
+ }
84
+
85
+ function handler(client: SharedGmailClient, name: string): ToolHandler {
86
+ return async (args, context) => {
87
+ switch (name) {
88
+ case "list_messages":
89
+ return objectResult(
90
+ await client.listMessages(context, {
91
+ ...(args.query === undefined ? {} : { query: args.query as string }),
92
+ ...(args.max_results === undefined ? {} : { maxResults: args.max_results as number }),
93
+ ...(args.page_token === undefined ? {} : { pageToken: args.page_token as string }),
94
+ }),
95
+ );
96
+ case "get_message":
97
+ return objectResult(
98
+ await client.getMessage(context, { messageId: args.message_id as string }),
99
+ );
100
+ case "send_message":
101
+ return objectResult(await client.sendMessage(context, { raw: args.raw as string }));
102
+ default:
103
+ throw new Error("Unknown Gmail tool");
104
+ }
105
+ };
106
+ }
107
+
108
+ export function createSharedGmailToolBindings(
109
+ client: SharedGmailClient,
110
+ ): [ToolSpec, ToolHandler][] {
111
+ return specs().map((spec) => [spec, handler(client, spec.name)]);
112
+ }
113
+
114
+ export class GmailIntegration extends Integration {
115
+ readonly namespace = "gmail";
116
+ private closeOwnedRequester: (() => void) | undefined;
117
+
118
+ constructor(
119
+ private readonly client: SharedGmailClient,
120
+ closeOwnedRequester?: () => void,
121
+ ) {
122
+ super();
123
+ this.closeOwnedRequester = closeOwnedRequester;
124
+ }
125
+
126
+ override tools(): [ToolSpec, ToolHandler][] {
127
+ return createSharedGmailToolBindings(this.client);
128
+ }
129
+
130
+ close(): void {
131
+ const close = this.closeOwnedRequester;
132
+ if (close === undefined) return;
133
+ close();
134
+ this.closeOwnedRequester = undefined;
135
+ }
136
+ }
137
+
138
+ export interface CreateGmailIntegrationOptions {
139
+ readonly tokenFor: (context: ToolExecutionContext) => Promise<string>;
140
+ readonly metricsSink?: MetricsSink;
141
+ readonly traceSink?: TraceSink;
142
+ }
143
+
144
+ export function createGmailIntegration(options: CreateGmailIntegrationOptions): GmailIntegration {
145
+ const { metricsSink, traceSink, ...clientOptions } = options;
146
+ const http = createGmailRequester({ metricsSink, traceSink });
147
+ return new GmailIntegration(new GmailClient({ ...clientOptions, http }), () => http.close());
148
+ }
149
+
150
+ function createGmailIntegrationForTest(client: SharedGmailClient): GmailIntegration {
151
+ return new GmailIntegration(client);
152
+ }
153
+
154
+ const inspectionClient = new Proxy(
155
+ {},
156
+ {
157
+ get: () => async () => {
158
+ throw new Error("inspection dependencies must not execute");
159
+ },
160
+ },
161
+ ) as SharedGmailClient;
162
+
163
+ export function inspectIntegration(): GmailIntegration {
164
+ return createGmailIntegrationForTest(inspectionClient);
165
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "name": "gmail",
3
+ "version": "0.1.0",
4
+ "namespace": "gmail",
5
+ "description": "Mailbox-scoped Gmail read and send tools for the authenticated user.",
6
+ "auth": {
7
+ "kind": "oauth",
8
+ "provider": "google",
9
+ "clientIdEnv": "GOOGLE_CLIENT_ID",
10
+ "clientSecretEnv": "GOOGLE_CLIENT_SECRET",
11
+ "scopes": [
12
+ "https://www.googleapis.com/auth/gmail.readonly",
13
+ "https://www.googleapis.com/auth/gmail.send"
14
+ ],
15
+ "docs": "https://developers.google.com/gmail/api/auth/scopes"
16
+ },
17
+ "files": [
18
+ "index.ts",
19
+ "client.ts",
20
+ "tests/gmail.test.ts",
21
+ "owner-fixtures.json",
22
+ "LICENSE"
23
+ ],
24
+ "tools": [
25
+ {
26
+ "name": "get_message",
27
+ "description": "Get a message from the authenticated user's mailbox.",
28
+ "parameters": {
29
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
30
+ "type": "object",
31
+ "properties": {
32
+ "message_id": {
33
+ "type": "string",
34
+ "minLength": 1,
35
+ "maxLength": 128
36
+ }
37
+ },
38
+ "required": [
39
+ "message_id"
40
+ ],
41
+ "additionalProperties": false
42
+ },
43
+ "risk": "read",
44
+ "parallel_safe": true,
45
+ "timeout_ms": 10000
46
+ },
47
+ {
48
+ "name": "list_messages",
49
+ "description": "List messages in the authenticated user's mailbox. Pass `page_token` from a prior result's `next_page_token` to page through results.",
50
+ "parameters": {
51
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
52
+ "type": "object",
53
+ "properties": {
54
+ "query": {
55
+ "type": "string",
56
+ "minLength": 1,
57
+ "maxLength": 1024
58
+ },
59
+ "max_results": {
60
+ "type": "integer",
61
+ "minimum": 1,
62
+ "maximum": 100,
63
+ "default": 10
64
+ },
65
+ "page_token": {
66
+ "type": "string",
67
+ "minLength": 1,
68
+ "maxLength": 2048
69
+ }
70
+ },
71
+ "required": [],
72
+ "additionalProperties": false
73
+ },
74
+ "risk": "read",
75
+ "parallel_safe": true,
76
+ "timeout_ms": 10000
77
+ },
78
+ {
79
+ "name": "send_message",
80
+ "description": "Send an email as the authenticated user. `raw` is the complete RFC 2822 message, base64url-encoded.",
81
+ "parameters": {
82
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
83
+ "type": "object",
84
+ "properties": {
85
+ "raw": {
86
+ "type": "string",
87
+ "minLength": 1,
88
+ "maxLength": 1048576
89
+ }
90
+ },
91
+ "required": [
92
+ "raw"
93
+ ],
94
+ "additionalProperties": false
95
+ },
96
+ "risk": "external_effect",
97
+ "parallel_safe": false,
98
+ "timeout_ms": 15000
99
+ }
100
+ ],
101
+ "peerDeps": {}
102
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "outcomes": [
4
+ { "name": "success", "expected": "success" },
5
+ { "name": "missing_auth", "expected": "INTEGRATION_AUTH_REQUIRED" },
6
+ { "name": "rate_limit", "expected": "INTEGRATION_RATE_LIMITED" },
7
+ { "name": "approval_rejected", "expected": "APPROVAL_REJECTED" },
8
+ { "name": "connection_lost_after_dispatch", "expected": "unknown" }
9
+ ]
10
+ }
@@ -0,0 +1,32 @@
1
+ // This is YOUR no-network Gmail owner fixture test. Edit it.
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const closedOutcomes = new Set([
7
+ "success",
8
+ "missing_auth",
9
+ "rate_limit",
10
+ "approval_rejected",
11
+ "connection_lost_after_dispatch",
12
+ ]);
13
+
14
+ export function scriptedOutcome(name: string): string {
15
+ if (!closedOutcomes.has(name)) throw new Error("unknown scripted Gmail outcome");
16
+ const fixture = JSON.parse(
17
+ readFileSync(new URL("../owner-fixtures.json", import.meta.url), "utf8"),
18
+ ) as { outcomes: Array<{ name: string; expected: string }> };
19
+ return fixture.outcomes.find((row) => row.name === name)!.expected;
20
+ }
21
+
22
+ describe("Gmail owner fixtures", () => {
23
+ it("keeps scripted outcomes closed and network-free", () => {
24
+ expect([...closedOutcomes].map((name) => [name, scriptedOutcome(name)])).toEqual([
25
+ ["success", "success"],
26
+ ["missing_auth", "INTEGRATION_AUTH_REQUIRED"],
27
+ ["rate_limit", "INTEGRATION_RATE_LIMITED"],
28
+ ["approval_rejected", "APPROVAL_REJECTED"],
29
+ ["connection_lost_after_dispatch", "unknown"],
30
+ ]);
31
+ });
32
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "./index.schema.json",
3
+ "version": "0.1.0",
4
+ "integrations": {
5
+ "echo": {
6
+ "manifest": "echo/manifest.json",
7
+ "stability": "beta",
8
+ "runtimes": ["python", "typescript"]
9
+ },
10
+ "gmail": {
11
+ "manifest": "gmail/manifest.json",
12
+ "stability": "beta",
13
+ "runtimes": ["python", "typescript"]
14
+ },
15
+ "github": {
16
+ "manifest": "github/manifest.json",
17
+ "stability": "beta",
18
+ "runtimes": ["python", "typescript"]
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kaji.dev/schemas/integration-index-v1.json",
4
+ "title": "Kaji integration registry index",
5
+ "type": "object",
6
+ "required": ["$schema", "version", "integrations"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "$schema": { "const": "./index.schema.json" },
10
+ "version": {
11
+ "type": "string",
12
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
13
+ },
14
+ "integrations": {
15
+ "type": "object",
16
+ "propertyNames": { "pattern": "^[a-z][a-z0-9_-]*$" },
17
+ "additionalProperties": {
18
+ "type": "object",
19
+ "required": ["manifest", "stability", "runtimes"],
20
+ "additionalProperties": false,
21
+ "properties": {
22
+ "manifest": {
23
+ "type": "string",
24
+ "pattern": "^(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!.*(?:^|/)\\.\\.?(?:/|$))[A-Za-z0-9_@.+-]+(?:/[A-Za-z0-9_@.+-]+)*$"
25
+ },
26
+ "stability": { "enum": ["experimental", "beta"] },
27
+ "runtimes": {
28
+ "type": "array",
29
+ "minItems": 1,
30
+ "uniqueItems": true,
31
+ "items": { "enum": ["python", "typescript"] }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,119 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kaji.dev/schemas/integration-manifest-v1.json",
4
+ "title": "Kaji integration manifest",
5
+ "type": "object",
6
+ "required": ["name", "version", "namespace", "description", "auth", "files", "tools"],
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "maxLength": 128,
12
+ "pattern": "^[a-z][a-z0-9_-]*$"
13
+ },
14
+ "version": {
15
+ "type": "string",
16
+ "pattern": "^\\d+\\.\\d+\\.\\d+$"
17
+ },
18
+ "namespace": {
19
+ "type": "string",
20
+ "pattern": "^[a-z][a-z0-9_]*$"
21
+ },
22
+ "description": {
23
+ "type": "string",
24
+ "minLength": 1
25
+ },
26
+ "auth": {
27
+ "oneOf": [
28
+ {
29
+ "type": "object",
30
+ "required": ["kind"],
31
+ "properties": {
32
+ "kind": { "const": "none" }
33
+ },
34
+ "additionalProperties": false
35
+ },
36
+ {
37
+ "type": "object",
38
+ "required": ["kind", "env"],
39
+ "properties": {
40
+ "kind": { "const": "env" },
41
+ "env": {
42
+ "type": "string",
43
+ "pattern": "^[A-Z][A-Z0-9_]*$"
44
+ },
45
+ "optional": { "type": "boolean", "default": false },
46
+ "docs": { "type": "string", "format": "uri" }
47
+ },
48
+ "additionalProperties": false
49
+ },
50
+ {
51
+ "type": "object",
52
+ "required": ["kind", "provider", "clientIdEnv", "scopes"],
53
+ "properties": {
54
+ "kind": { "const": "oauth" },
55
+ "provider": { "enum": ["google"] },
56
+ "clientIdEnv": {
57
+ "type": "string",
58
+ "pattern": "^[A-Z][A-Z0-9_]*$"
59
+ },
60
+ "clientSecretEnv": {
61
+ "type": "string",
62
+ "pattern": "^[A-Z][A-Z0-9_]*$"
63
+ },
64
+ "scopes": {
65
+ "type": "array",
66
+ "minItems": 1,
67
+ "uniqueItems": true,
68
+ "items": { "type": "string", "minLength": 1, "maxLength": 512 }
69
+ },
70
+ "docs": { "type": "string", "format": "uri" }
71
+ },
72
+ "additionalProperties": false
73
+ }
74
+ ]
75
+ },
76
+ "files": {
77
+ "type": "array",
78
+ "minItems": 1,
79
+ "uniqueItems": true,
80
+ "items": {
81
+ "type": "string",
82
+ "pattern": "^(?!/)(?![A-Za-z]:)(?!.*\\\\)(?!.*(?:^|/)\\.\\.?(?:/|$))[A-Za-z0-9_@.+-]+(?:/[A-Za-z0-9_@.+-]+)*$"
83
+ }
84
+ },
85
+ "tools": {
86
+ "type": "array",
87
+ "minItems": 1,
88
+ "uniqueItems": true,
89
+ "items": {
90
+ "type": "object",
91
+ "required": ["name", "description", "parameters", "risk", "parallel_safe"],
92
+ "additionalProperties": false,
93
+ "properties": {
94
+ "name": {
95
+ "type": "string",
96
+ "pattern": "^[a-z][a-z0-9_]*$"
97
+ },
98
+ "description": { "type": "string", "minLength": 1 },
99
+ "parameters": { "type": "object" },
100
+ "risk": {
101
+ "enum": ["read", "write", "external_effect", "destructive", "admin"]
102
+ },
103
+ "parallel_safe": { "type": "boolean" },
104
+ "timeout_ms": { "type": "integer", "minimum": 1 }
105
+ }
106
+ }
107
+ },
108
+ "extras": {
109
+ "type": "array",
110
+ "uniqueItems": true,
111
+ "items": { "type": "string", "minLength": 1 }
112
+ },
113
+ "peerDeps": {
114
+ "type": "object",
115
+ "propertyNames": { "minLength": 1 },
116
+ "additionalProperties": { "type": "string", "minLength": 1 }
117
+ }
118
+ }
119
+ }