@zackbart/connecta 0.15.1 → 0.16.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/AGENTS.md +40 -20
- package/CHANGELOG.md +653 -0
- package/README.md +25 -5
- package/bin/connecta.mjs +44 -3
- package/dist/access-tokens.d.ts +0 -1
- package/dist/access-tokens.js +0 -1
- package/dist/activity.d.ts +50 -2
- package/dist/activity.js +34 -1
- package/dist/apps-shell.d.ts +0 -1
- package/dist/apps-shell.js +0 -1
- package/dist/auth/bearer.d.ts +0 -1
- package/dist/auth/bearer.js +0 -1
- package/dist/auth/clerk.d.ts +0 -1
- package/dist/auth/clerk.js +45 -9
- package/dist/auth/downstream-oauth.d.ts +0 -1
- package/dist/auth/downstream-oauth.js +0 -1
- package/dist/call-admission.d.ts +0 -1
- package/dist/call-admission.js +0 -1
- package/dist/catalog-drift.d.ts +99 -0
- package/dist/catalog-drift.js +251 -0
- package/dist/catalog-fingerprint.d.ts +0 -1
- package/dist/catalog-fingerprint.js +0 -1
- package/dist/catalog-limits.d.ts +0 -1
- package/dist/catalog-limits.js +0 -1
- package/dist/catalog-service.d.ts +0 -1
- package/dist/catalog-service.js +41 -5
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +20 -1
- package/dist/concurrency.d.ts +0 -1
- package/dist/concurrency.js +0 -1
- package/dist/connector-scope.d.ts +0 -1
- package/dist/connector-scope.js +0 -1
- package/dist/connectors/api.d.ts +29 -21
- package/dist/connectors/api.js +45 -19
- package/dist/connectors/guarded-fetch.d.ts +106 -0
- package/dist/connectors/guarded-fetch.js +289 -0
- package/dist/connectors/remote-mcp.d.ts +0 -1
- package/dist/connectors/remote-mcp.js +0 -1
- package/dist/credentials.d.ts +0 -1
- package/dist/credentials.js +0 -1
- package/dist/errors.d.ts +30 -3
- package/dist/errors.js +1 -1
- package/dist/execute.d.ts +0 -1
- package/dist/execute.js +0 -1
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +28 -1
- package/dist/executor-result.d.ts +0 -1
- package/dist/executor-result.js +0 -1
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-child.js +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -1
- package/dist/executors/quickjs-protocol.js +0 -1
- package/dist/executors/quickjs-runtime.d.ts +0 -1
- package/dist/executors/quickjs-runtime.js +0 -1
- package/dist/executors/quickjs.d.ts +0 -1
- package/dist/executors/quickjs.js +2 -1
- package/dist/favicon.d.ts +0 -1
- package/dist/favicon.js +0 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +24 -7
- package/dist/invocation.d.ts +0 -1
- package/dist/invocation.js +0 -1
- package/dist/json-schema.d.ts +0 -1
- package/dist/json-schema.js +0 -1
- package/dist/mcp-result.d.ts +0 -1
- package/dist/mcp-result.js +0 -1
- package/dist/meta-tools.d.ts +0 -1
- package/dist/meta-tools.js +0 -1
- package/dist/node.d.ts +0 -1
- package/dist/node.js +0 -1
- package/dist/operator-ui/generated.d.ts +2 -3
- package/dist/operator-ui/generated.js +3 -4
- package/dist/operator-ui/model.d.ts +10 -1
- package/dist/operator-ui/model.js +0 -1
- package/dist/operator-ui/view.d.ts +167 -0
- package/dist/operator-ui/view.js +270 -0
- package/dist/providers/cloudflare.d.ts +0 -1
- package/dist/providers/cloudflare.js +261 -299
- package/dist/providers/linear.d.ts +26 -5
- package/dist/providers/linear.js +32 -61
- package/dist/providers/mixpanel.d.ts +35 -4
- package/dist/providers/mixpanel.js +53 -79
- package/dist/providers/notion.d.ts +0 -1
- package/dist/providers/notion.js +87 -42
- package/dist/providers/stripe.d.ts +13 -1
- package/dist/providers/stripe.js +31 -60
- package/dist/registry.d.ts +34 -2
- package/dist/registry.js +85 -5
- package/dist/retired-toolkits.d.ts +0 -1
- package/dist/retired-toolkits.js +0 -1
- package/dist/routes/access-tokens.d.ts +0 -1
- package/dist/routes/access-tokens.js +0 -1
- package/dist/routes/activity.d.ts +0 -1
- package/dist/routes/activity.js +0 -1
- package/dist/routes/credentials.d.ts +0 -1
- package/dist/routes/credentials.js +0 -1
- package/dist/routes/mcp.d.ts +0 -1
- package/dist/routes/mcp.js +0 -1
- package/dist/routes/oauth.d.ts +0 -1
- package/dist/routes/oauth.js +0 -1
- package/dist/routes/shared.d.ts +2 -1
- package/dist/routes/shared.js +0 -1
- package/dist/routes/ui.d.ts +0 -1
- package/dist/routes/ui.js +0 -1
- package/dist/server.d.ts +0 -1
- package/dist/server.js +11 -1
- package/dist/skills.d.ts +0 -1
- package/dist/skills.js +0 -1
- package/dist/storage/file.d.ts +0 -1
- package/dist/storage/file.js +0 -1
- package/dist/storage/memory.d.ts +0 -1
- package/dist/storage/memory.js +0 -1
- package/dist/timeout.d.ts +0 -1
- package/dist/timeout.js +0 -1
- package/dist/tool-safety.d.ts +0 -1
- package/dist/tool-safety.js +0 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +0 -1
- package/dist/ui.js +13 -137
- package/dist/validate.d.ts +13 -15
- package/dist/validate.js +24 -11
- package/dist/version.d.ts +1 -2
- package/dist/version.js +1 -2
- package/documentation/architecture.md +186 -5
- package/documentation/auth.md +11 -0
- package/documentation/call-admission.md +142 -6
- package/documentation/cloudflare.md +92 -10
- package/documentation/code-first-exploration.md +2 -2
- package/documentation/code-mode.md +14 -14
- package/documentation/connector-guides.md +172 -5
- package/documentation/connectors.md +89 -0
- package/documentation/linear.md +20 -2
- package/documentation/mcp-ui-design.md +2 -2
- package/documentation/meta-tools.md +7 -1
- package/documentation/mixpanel.md +42 -9
- package/documentation/notion.md +23 -2
- package/documentation/operations.md +325 -5
- package/documentation/operator-ui.md +120 -5
- package/documentation/provider-audit.md +160 -0
- package/documentation/provider-conventions.md +667 -0
- package/documentation/request-admission.md +167 -5
- package/documentation/rich-output-design.md +1 -2
- package/documentation/stripe.md +15 -0
- package/documentation/upgrading.md +454 -0
- package/ethos.md +14 -9
- package/examples/worker/README.md +102 -12
- package/examples/worker/src/index.ts +46 -1
- package/examples/worker/wrangler.jsonc +14 -1
- package/package.json +10 -6
- package/templates/node/.dockerignore +10 -0
- package/templates/node/.env.example +40 -3
- package/templates/node/AGENTS.md +16 -1
- package/templates/node/Dockerfile +49 -0
- package/templates/node/README.md +117 -2
- package/templates/node/docker-compose.yml +39 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/file-activity.ts +182 -0
- package/templates/node/src/index.ts +80 -4
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/access-tokens.d.ts.map +0 -1
- package/dist/access-tokens.js.map +0 -1
- package/dist/activity.d.ts.map +0 -1
- package/dist/activity.js.map +0 -1
- package/dist/apps-shell.d.ts.map +0 -1
- package/dist/apps-shell.js.map +0 -1
- package/dist/auth/bearer.d.ts.map +0 -1
- package/dist/auth/bearer.js.map +0 -1
- package/dist/auth/clerk.d.ts.map +0 -1
- package/dist/auth/clerk.js.map +0 -1
- package/dist/auth/downstream-oauth.d.ts.map +0 -1
- package/dist/auth/downstream-oauth.js.map +0 -1
- package/dist/call-admission.d.ts.map +0 -1
- package/dist/call-admission.js.map +0 -1
- package/dist/catalog-fingerprint.d.ts.map +0 -1
- package/dist/catalog-fingerprint.js.map +0 -1
- package/dist/catalog-limits.d.ts.map +0 -1
- package/dist/catalog-limits.js.map +0 -1
- package/dist/catalog-service.d.ts.map +0 -1
- package/dist/catalog-service.js.map +0 -1
- package/dist/catalog.d.ts.map +0 -1
- package/dist/catalog.js.map +0 -1
- package/dist/concurrency.d.ts.map +0 -1
- package/dist/concurrency.js.map +0 -1
- package/dist/connector-scope.d.ts.map +0 -1
- package/dist/connector-scope.js.map +0 -1
- package/dist/connectors/api.d.ts.map +0 -1
- package/dist/connectors/api.js.map +0 -1
- package/dist/connectors/remote-mcp.d.ts.map +0 -1
- package/dist/connectors/remote-mcp.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js.map +0 -1
- package/dist/executor-admission.d.ts.map +0 -1
- package/dist/executor-admission.js.map +0 -1
- package/dist/executor-result.d.ts.map +0 -1
- package/dist/executor-result.js.map +0 -1
- package/dist/executors/quickjs-child.d.ts.map +0 -1
- package/dist/executors/quickjs-child.js.map +0 -1
- package/dist/executors/quickjs-protocol.d.ts.map +0 -1
- package/dist/executors/quickjs-protocol.js.map +0 -1
- package/dist/executors/quickjs-runtime.d.ts.map +0 -1
- package/dist/executors/quickjs-runtime.js.map +0 -1
- package/dist/executors/quickjs.d.ts.map +0 -1
- package/dist/executors/quickjs.js.map +0 -1
- package/dist/favicon.d.ts.map +0 -1
- package/dist/favicon.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/invocation.d.ts.map +0 -1
- package/dist/invocation.js.map +0 -1
- package/dist/json-schema.d.ts.map +0 -1
- package/dist/json-schema.js.map +0 -1
- package/dist/mcp-result.d.ts.map +0 -1
- package/dist/mcp-result.js.map +0 -1
- package/dist/meta-tools.d.ts.map +0 -1
- package/dist/meta-tools.js.map +0 -1
- package/dist/node.d.ts.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/operator-ui/generated.d.ts.map +0 -1
- package/dist/operator-ui/generated.js.map +0 -1
- package/dist/operator-ui/model.d.ts.map +0 -1
- package/dist/operator-ui/model.js.map +0 -1
- package/dist/providers/cloudflare.d.ts.map +0 -1
- package/dist/providers/cloudflare.js.map +0 -1
- package/dist/providers/linear.d.ts.map +0 -1
- package/dist/providers/linear.js.map +0 -1
- package/dist/providers/mixpanel.d.ts.map +0 -1
- package/dist/providers/mixpanel.js.map +0 -1
- package/dist/providers/notion.d.ts.map +0 -1
- package/dist/providers/notion.js.map +0 -1
- package/dist/providers/stripe.d.ts.map +0 -1
- package/dist/providers/stripe.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/retired-toolkits.d.ts.map +0 -1
- package/dist/retired-toolkits.js.map +0 -1
- package/dist/routes/access-tokens.d.ts.map +0 -1
- package/dist/routes/access-tokens.js.map +0 -1
- package/dist/routes/activity.d.ts.map +0 -1
- package/dist/routes/activity.js.map +0 -1
- package/dist/routes/credentials.d.ts.map +0 -1
- package/dist/routes/credentials.js.map +0 -1
- package/dist/routes/mcp.d.ts.map +0 -1
- package/dist/routes/mcp.js.map +0 -1
- package/dist/routes/oauth.d.ts.map +0 -1
- package/dist/routes/oauth.js.map +0 -1
- package/dist/routes/shared.d.ts.map +0 -1
- package/dist/routes/shared.js.map +0 -1
- package/dist/routes/ui.d.ts.map +0 -1
- package/dist/routes/ui.js.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js.map +0 -1
- package/dist/storage/file.d.ts.map +0 -1
- package/dist/storage/file.js.map +0 -1
- package/dist/storage/memory.d.ts.map +0 -1
- package/dist/storage/memory.js.map +0 -1
- package/dist/timeout.d.ts.map +0 -1
- package/dist/timeout.js.map +0 -1
- package/dist/tool-safety.d.ts.map +0 -1
- package/dist/tool-safety.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/ui.d.ts.map +0 -1
- package/dist/ui.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
- package/examples/node/README.md +0 -52
- package/examples/node/src/index.ts +0 -71
- package/src/access-tokens.ts +0 -289
- package/src/activity.ts +0 -234
- package/src/apps-shell.ts +0 -370
- package/src/auth/bearer.ts +0 -61
- package/src/auth/clerk.ts +0 -553
- package/src/auth/downstream-oauth.ts +0 -575
- package/src/call-admission.ts +0 -521
- package/src/catalog-fingerprint.ts +0 -52
- package/src/catalog-limits.ts +0 -11
- package/src/catalog-service.ts +0 -1153
- package/src/catalog.ts +0 -790
- package/src/concurrency.ts +0 -39
- package/src/connector-scope.ts +0 -66
- package/src/connectors/api.ts +0 -159
- package/src/connectors/remote-mcp.ts +0 -1090
- package/src/credentials.ts +0 -428
- package/src/errors.ts +0 -364
- package/src/execute.ts +0 -1406
- package/src/executor-admission.ts +0 -328
- package/src/executor-result.ts +0 -107
- package/src/executors/quickjs-child.ts +0 -172
- package/src/executors/quickjs-protocol.ts +0 -99
- package/src/executors/quickjs-runtime.ts +0 -470
- package/src/executors/quickjs.ts +0 -776
- package/src/favicon.ts +0 -53
- package/src/index.ts +0 -666
- package/src/invocation.ts +0 -655
- package/src/json-schema.ts +0 -11
- package/src/mcp-result.ts +0 -43
- package/src/meta-tools.ts +0 -1607
- package/src/node.ts +0 -206
- package/src/operator-ui/browser.css +0 -423
- package/src/operator-ui/browser.ts +0 -1281
- package/src/operator-ui/generated.ts +0 -4
- package/src/operator-ui/model.ts +0 -111
- package/src/providers/cloudflare.ts +0 -3999
- package/src/providers/linear.ts +0 -302
- package/src/providers/mixpanel.ts +0 -229
- package/src/providers/notion.ts +0 -1879
- package/src/providers/stripe.ts +0 -307
- package/src/registry.ts +0 -1085
- package/src/retired-toolkits.ts +0 -14
- package/src/routes/access-tokens.ts +0 -115
- package/src/routes/activity.ts +0 -215
- package/src/routes/credentials.ts +0 -254
- package/src/routes/mcp.ts +0 -429
- package/src/routes/oauth.ts +0 -322
- package/src/routes/shared.ts +0 -247
- package/src/routes/ui.ts +0 -152
- package/src/server.ts +0 -195
- package/src/skills.ts +0 -271
- package/src/storage/file.ts +0 -123
- package/src/storage/memory.ts +0 -44
- package/src/timeout.ts +0 -84
- package/src/tool-safety.ts +0 -15
- package/src/types.ts +0 -523
- package/src/ui.ts +0 -744
- package/src/validate.ts +0 -387
- package/src/version.ts +0 -7
package/examples/node/README.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# connecta — Node repository example
|
|
2
|
-
|
|
3
|
-
This example runs against the current package source. For an independently
|
|
4
|
-
installable deployment, use the root initializer instead:
|
|
5
|
-
|
|
6
|
-
```sh
|
|
7
|
-
npx @zackbart/connecta init my-connecta
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
From the package repository, run this example with:
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
npm install
|
|
14
|
-
CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
It serves the prescribed seven-tool code-first surface: `execute_code` in a
|
|
18
|
-
bounded QuickJS child plus the six explicit boundary tools.
|
|
19
|
-
|
|
20
|
-
- MCP endpoint: `http://localhost:8787/mcp`, with
|
|
21
|
-
`Authorization: Bearer dev-token`
|
|
22
|
-
- Operator pages: Connections at `http://localhost:8787/`, Credentials at
|
|
23
|
-
`/credentials`, and Activity at `/activity` (paste the same token; this
|
|
24
|
-
bearer-only example cannot manage credentials)
|
|
25
|
-
- Health: `http://localhost:8787/health`
|
|
26
|
-
|
|
27
|
-
`PORT` defaults to `8787`; `CONNECTA_TOKEN` is required. Use a long random
|
|
28
|
-
value outside this local example.
|
|
29
|
-
|
|
30
|
-
## The deployment contract
|
|
31
|
-
|
|
32
|
-
Keep this deployment small:
|
|
33
|
-
|
|
34
|
-
- Edit `src/index.ts` to change connectors, auth, storage, and the public URL.
|
|
35
|
-
- Keep the required `executor: quickJsExecutor()` configuration.
|
|
36
|
-
- Keep secrets in environment variables or an external secret store. Never put
|
|
37
|
-
tokens in `src/index.ts`, connector guides, or committed JSON.
|
|
38
|
-
- Add application code only when implementing a deliberate `api()` connector.
|
|
39
|
-
Do not copy Connecta internals into the deployment.
|
|
40
|
-
- Run the repository's `npm run check` after a package change.
|
|
41
|
-
|
|
42
|
-
`fileStorage("./.connecta-state.json")` persists downstream OAuth tokens and
|
|
43
|
-
tool catalogs across restarts. `memoryStorage()` is sufficient only when the
|
|
44
|
-
deployment needs neither. Add `remoteMcp(...)` entries for downstream MCP
|
|
45
|
-
servers or `api(...)` connectors for HTTP APIs you deliberately expose.
|
|
46
|
-
Downstream OAuth also requires `publicUrl` to be an origin the browser can
|
|
47
|
-
reach.
|
|
48
|
-
|
|
49
|
-
Docker packaging of the same code-first shape is
|
|
50
|
-
[repository-only](https://github.com/zackbart/connecta/tree/main/examples/docker).
|
|
51
|
-
The standalone template used by the initializer is in
|
|
52
|
-
[`../../templates/node/`](../../templates/node/).
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* connecta on Node.
|
|
3
|
-
*
|
|
4
|
-
* One MCP endpoint aggregating two in-code HTTP API connectors behind the
|
|
5
|
-
* seven-tool code-first surface (execute_code in a QuickJS/WASM sandbox plus the
|
|
6
|
-
* six explicit tools), guarded by a static bearer token, with OAuth/cache state
|
|
7
|
-
* on disk.
|
|
8
|
-
*
|
|
9
|
-
* Run:
|
|
10
|
-
* CONNECTA_TOKEN=dev-token npx tsx examples/node/src/index.ts
|
|
11
|
-
* # then point an MCP client at http://localhost:8787/mcp with
|
|
12
|
-
* # Authorization: Bearer dev-token
|
|
13
|
-
*/
|
|
14
|
-
import { api, bearerToken, createConnecta } from "@zackbart/connecta";
|
|
15
|
-
import { fileStorage, listen } from "@zackbart/connecta/node";
|
|
16
|
-
import { quickJsExecutor } from "@zackbart/connecta/quickjs";
|
|
17
|
-
|
|
18
|
-
const token = process.env.CONNECTA_TOKEN;
|
|
19
|
-
if (!token) {
|
|
20
|
-
throw new Error(
|
|
21
|
-
"CONNECTA_TOKEN is required. Refusing to start without inbound auth.",
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
const port = Number(process.env.PORT ?? 8787);
|
|
25
|
-
|
|
26
|
-
const connecta = createConnecta({
|
|
27
|
-
// fileStorage persists downstream-OAuth/cache state across restarts.
|
|
28
|
-
// Swap for memoryStorage() if you don't need persistence.
|
|
29
|
-
storage: fileStorage("./.connecta-state.json"),
|
|
30
|
-
auth: bearerToken(token, { subjectId: "operator" }),
|
|
31
|
-
// Downstream OAuth callbacks use this deployment origin.
|
|
32
|
-
publicUrl: `http://localhost:${port}`,
|
|
33
|
-
// Required: QuickJS runs model-written JS in a bounded disposable child.
|
|
34
|
-
executor: quickJsExecutor(),
|
|
35
|
-
connectors: [
|
|
36
|
-
api("time", {
|
|
37
|
-
description: "Time — current timestamp",
|
|
38
|
-
tools: [
|
|
39
|
-
{
|
|
40
|
-
name: "get_now",
|
|
41
|
-
description: "Return the current time as an ISO 8601 timestamp.",
|
|
42
|
-
inputSchema: { type: "object", properties: {} },
|
|
43
|
-
annotations: { readOnlyHint: true },
|
|
44
|
-
handler: async () => ({ now: new Date().toISOString() }),
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
}),
|
|
48
|
-
api("text", {
|
|
49
|
-
description: "Text — string utilities",
|
|
50
|
-
tools: [
|
|
51
|
-
{
|
|
52
|
-
name: "upper",
|
|
53
|
-
description: "Uppercase the given text.",
|
|
54
|
-
inputSchema: {
|
|
55
|
-
type: "object",
|
|
56
|
-
properties: { text: { type: "string" } },
|
|
57
|
-
required: ["text"],
|
|
58
|
-
},
|
|
59
|
-
annotations: { readOnlyHint: true },
|
|
60
|
-
handler: async ({ text }: { text: string }) => ({
|
|
61
|
-
text: text.toUpperCase(),
|
|
62
|
-
}),
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
}),
|
|
66
|
-
],
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
listen(connecta, port);
|
|
70
|
-
|
|
71
|
-
console.log(`connecta listening on http://localhost:${port}/mcp`);
|
package/src/access-tokens.ts
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import type { AuthResult, InboundAuth, KVStorage } from "./types.js";
|
|
2
|
-
|
|
3
|
-
const TOKEN_PREFIX = "cta_";
|
|
4
|
-
const TOKEN_BYTES = 32;
|
|
5
|
-
const TOKEN_VALUE_RE = /^cta_[A-Za-z0-9_-]{43}$/;
|
|
6
|
-
const RECORD_PREFIX = "access-token:v1:record:";
|
|
7
|
-
const LOOKUP_PREFIX = "access-token:v1:lookup:";
|
|
8
|
-
const MAX_NAME_CHARACTERS = 80;
|
|
9
|
-
const DEFAULT_MAX_ACTIVE = 100;
|
|
10
|
-
const MAX_CONFIGURED_ACTIVE = 1_000;
|
|
11
|
-
const encoder = new TextEncoder();
|
|
12
|
-
|
|
13
|
-
interface StoredAccessToken {
|
|
14
|
-
version: 1;
|
|
15
|
-
id: string;
|
|
16
|
-
name: string;
|
|
17
|
-
tokenHash: string;
|
|
18
|
-
tokenPrefix: string;
|
|
19
|
-
createdAt: string;
|
|
20
|
-
createdBy: string;
|
|
21
|
-
revokedAt?: string;
|
|
22
|
-
revokedBy?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface TokenLookup {
|
|
26
|
-
version: 1;
|
|
27
|
-
id: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface AccessTokenMetadata {
|
|
31
|
-
id: string;
|
|
32
|
-
name: string;
|
|
33
|
-
tokenPrefix: string;
|
|
34
|
-
createdAt: string;
|
|
35
|
-
revokedAt?: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface CreatedAccessToken {
|
|
39
|
-
token: string;
|
|
40
|
-
accessToken: AccessTokenMetadata;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function recordKey(id: string): string {
|
|
44
|
-
return `${RECORD_PREFIX}${id}`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function lookupKey(hash: string): string {
|
|
48
|
-
return `${LOOKUP_PREFIX}${hash}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function bytesToBase64Url(bytes: Uint8Array): string {
|
|
52
|
-
let binary = "";
|
|
53
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
54
|
-
return btoa(binary)
|
|
55
|
-
.replaceAll("+", "-")
|
|
56
|
-
.replaceAll("/", "_")
|
|
57
|
-
.replace(/=+$/u, "");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function bytesToHex(bytes: Uint8Array): string {
|
|
61
|
-
return [...bytes]
|
|
62
|
-
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
63
|
-
.join("");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function hashToken(token: string): Promise<string> {
|
|
67
|
-
return bytesToHex(
|
|
68
|
-
new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(token))),
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function normalizeName(value: unknown): string {
|
|
73
|
-
if (typeof value !== "string") {
|
|
74
|
-
throw new Error("Token name must be a string");
|
|
75
|
-
}
|
|
76
|
-
const compact = value.replace(/\s+/gu, " ").trim();
|
|
77
|
-
if (!compact) throw new Error("Token name cannot be empty");
|
|
78
|
-
if (Array.from(compact).length > MAX_NAME_CHARACTERS) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Token name cannot exceed ${MAX_NAME_CHARACTERS} characters`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
return compact;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function parseRecord(raw: string): StoredAccessToken {
|
|
87
|
-
try {
|
|
88
|
-
const value = JSON.parse(raw) as Partial<StoredAccessToken>;
|
|
89
|
-
if (
|
|
90
|
-
value.version !== 1 ||
|
|
91
|
-
typeof value.id !== "string" ||
|
|
92
|
-
!/^[0-9a-f-]{36}$/u.test(value.id) ||
|
|
93
|
-
typeof value.name !== "string" ||
|
|
94
|
-
typeof value.tokenHash !== "string" ||
|
|
95
|
-
!/^[0-9a-f]{64}$/u.test(value.tokenHash) ||
|
|
96
|
-
typeof value.tokenPrefix !== "string" ||
|
|
97
|
-
typeof value.createdAt !== "string" ||
|
|
98
|
-
typeof value.createdBy !== "string" ||
|
|
99
|
-
(value.revokedAt !== undefined &&
|
|
100
|
-
typeof value.revokedAt !== "string") ||
|
|
101
|
-
(value.revokedBy !== undefined &&
|
|
102
|
-
typeof value.revokedBy !== "string")
|
|
103
|
-
) {
|
|
104
|
-
throw new Error("invalid token record");
|
|
105
|
-
}
|
|
106
|
-
return value as StoredAccessToken;
|
|
107
|
-
} catch {
|
|
108
|
-
throw new Error("Stored access token metadata is invalid or corrupted");
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function parseLookup(raw: string): TokenLookup | null {
|
|
113
|
-
try {
|
|
114
|
-
const value = JSON.parse(raw) as Partial<TokenLookup>;
|
|
115
|
-
return value.version === 1 && typeof value.id === "string"
|
|
116
|
-
? { version: 1, id: value.id }
|
|
117
|
-
: null;
|
|
118
|
-
} catch {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function metadata(record: StoredAccessToken): AccessTokenMetadata {
|
|
124
|
-
return {
|
|
125
|
-
id: record.id,
|
|
126
|
-
name: record.name,
|
|
127
|
-
tokenPrefix: record.tokenPrefix,
|
|
128
|
-
createdAt: record.createdAt,
|
|
129
|
-
...(record.revokedAt ? { revokedAt: record.revokedAt } : {}),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function unauthorized(): AuthResult {
|
|
134
|
-
return {
|
|
135
|
-
ok: false,
|
|
136
|
-
response: new Response(JSON.stringify({ error: "unauthorized" }), {
|
|
137
|
-
status: 401,
|
|
138
|
-
headers: {
|
|
139
|
-
"Content-Type": "application/json",
|
|
140
|
-
"WWW-Authenticate": "Bearer",
|
|
141
|
-
},
|
|
142
|
-
}),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Deployment-scoped personal access tokens. Secret material is never
|
|
148
|
-
* recoverable: authentication indexes a SHA-256 digest of a random 256-bit
|
|
149
|
-
* token, while separately enumerable metadata powers operator management.
|
|
150
|
-
*/
|
|
151
|
-
export class AccessTokenManager {
|
|
152
|
-
readonly auth: InboundAuth;
|
|
153
|
-
private readonly maxActive: number;
|
|
154
|
-
|
|
155
|
-
constructor(
|
|
156
|
-
private readonly storage: KVStorage,
|
|
157
|
-
options: { maxActive?: number } = {},
|
|
158
|
-
) {
|
|
159
|
-
if (!storage.list) {
|
|
160
|
-
throw new Error(
|
|
161
|
-
"accessTokens requires a storage adapter that implements list(prefix)",
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
const maxActive = options.maxActive ?? DEFAULT_MAX_ACTIVE;
|
|
165
|
-
if (
|
|
166
|
-
!Number.isInteger(maxActive) ||
|
|
167
|
-
maxActive < 1 ||
|
|
168
|
-
maxActive > MAX_CONFIGURED_ACTIVE
|
|
169
|
-
) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`accessTokens.maxActive must be a whole number from 1 to ${MAX_CONFIGURED_ACTIVE}`,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
this.maxActive = maxActive;
|
|
175
|
-
this.auth = {
|
|
176
|
-
kind: "access_token",
|
|
177
|
-
activityActorNamespace: "connecta:access-tokens:v1",
|
|
178
|
-
activityActorLabel: async (id) => {
|
|
179
|
-
try {
|
|
180
|
-
return (await this.read(id))?.name;
|
|
181
|
-
} catch {
|
|
182
|
-
return undefined;
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
authorize: (request) => this.authorize(request),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private async read(id: string): Promise<StoredAccessToken | null> {
|
|
190
|
-
const raw = await this.storage.get(recordKey(id));
|
|
191
|
-
return raw ? parseRecord(raw) : null;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async list(): Promise<AccessTokenMetadata[]> {
|
|
195
|
-
const keys = await this.storage.list!(RECORD_PREFIX);
|
|
196
|
-
const records = await Promise.all(
|
|
197
|
-
keys.map(async (key) => {
|
|
198
|
-
const raw = await this.storage.get(key);
|
|
199
|
-
return raw ? parseRecord(raw) : null;
|
|
200
|
-
}),
|
|
201
|
-
);
|
|
202
|
-
return records
|
|
203
|
-
.filter((record): record is StoredAccessToken => Boolean(record))
|
|
204
|
-
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
205
|
-
.map(metadata);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async create(name: unknown, createdBy: string): Promise<CreatedAccessToken> {
|
|
209
|
-
const normalizedName = normalizeName(name);
|
|
210
|
-
const active = (await this.list()).filter((token) => !token.revokedAt);
|
|
211
|
-
if (active.length >= this.maxActive) {
|
|
212
|
-
throw new Error(
|
|
213
|
-
`This deployment already has the maximum of ${this.maxActive} active access tokens`,
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
const secretBytes = crypto.getRandomValues(new Uint8Array(TOKEN_BYTES));
|
|
217
|
-
const token = TOKEN_PREFIX + bytesToBase64Url(secretBytes);
|
|
218
|
-
const hash = await hashToken(token);
|
|
219
|
-
if (await this.storage.get(lookupKey(hash))) {
|
|
220
|
-
throw new Error("Access token collision; create another token");
|
|
221
|
-
}
|
|
222
|
-
const record: StoredAccessToken = {
|
|
223
|
-
version: 1,
|
|
224
|
-
id: crypto.randomUUID(),
|
|
225
|
-
name: normalizedName,
|
|
226
|
-
tokenHash: hash,
|
|
227
|
-
tokenPrefix: token.slice(0, 12),
|
|
228
|
-
createdAt: new Date().toISOString(),
|
|
229
|
-
createdBy,
|
|
230
|
-
};
|
|
231
|
-
await this.storage.set(recordKey(record.id), JSON.stringify(record));
|
|
232
|
-
try {
|
|
233
|
-
await this.storage.set(
|
|
234
|
-
lookupKey(hash),
|
|
235
|
-
JSON.stringify({ version: 1, id: record.id } satisfies TokenLookup),
|
|
236
|
-
);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
await this.storage.delete(recordKey(record.id)).catch(() => {});
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
return { token, accessToken: metadata(record) };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async rename(
|
|
245
|
-
id: string,
|
|
246
|
-
name: unknown,
|
|
247
|
-
): Promise<AccessTokenMetadata | null> {
|
|
248
|
-
const record = await this.read(id);
|
|
249
|
-
if (!record) return null;
|
|
250
|
-
record.name = normalizeName(name);
|
|
251
|
-
await this.storage.set(recordKey(id), JSON.stringify(record));
|
|
252
|
-
return metadata(record);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
async revoke(
|
|
256
|
-
id: string,
|
|
257
|
-
revokedBy: string,
|
|
258
|
-
): Promise<AccessTokenMetadata | null> {
|
|
259
|
-
const record = await this.read(id);
|
|
260
|
-
if (!record) return null;
|
|
261
|
-
if (!record.revokedAt) {
|
|
262
|
-
// Admission disappears first. A metadata-write failure may leave the UI
|
|
263
|
-
// calling the record active, but can never leave a token labelled
|
|
264
|
-
// revoked while its lookup still admits requests.
|
|
265
|
-
await this.storage.delete(lookupKey(record.tokenHash));
|
|
266
|
-
record.revokedAt = new Date().toISOString();
|
|
267
|
-
record.revokedBy = revokedBy;
|
|
268
|
-
await this.storage.set(recordKey(id), JSON.stringify(record));
|
|
269
|
-
}
|
|
270
|
-
return metadata(record);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
private async authorize(request: Request): Promise<AuthResult> {
|
|
274
|
-
const header = request.headers.get("authorization") ?? "";
|
|
275
|
-
const match = /^Bearer\s+(.+)$/iu.exec(header);
|
|
276
|
-
const token = match?.[1];
|
|
277
|
-
if (!token || !TOKEN_VALUE_RE.test(token)) return unauthorized();
|
|
278
|
-
const hash = await hashToken(token);
|
|
279
|
-
const lookupRaw = await this.storage.get(lookupKey(hash));
|
|
280
|
-
if (!lookupRaw) return unauthorized();
|
|
281
|
-
const lookup = parseLookup(lookupRaw);
|
|
282
|
-
if (!lookup) return unauthorized();
|
|
283
|
-
const record = await this.read(lookup.id);
|
|
284
|
-
if (!record || record.revokedAt || record.tokenHash !== hash) {
|
|
285
|
-
return unauthorized();
|
|
286
|
-
}
|
|
287
|
-
return { ok: true, subjectId: record.id };
|
|
288
|
-
}
|
|
289
|
-
}
|
package/src/activity.ts
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { boundedEchoText } from "./errors.js";
|
|
2
|
-
import type { Logger } from "./types.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* How long an identity field may be before the store stops believing it.
|
|
6
|
-
*
|
|
7
|
-
* `connectorId` and `toolName` are ordinarily operator- and connector-authored,
|
|
8
|
-
* and 128 bytes is far past any real one. But an address that resolved to
|
|
9
|
-
* nothing is recorded *as written*, which puts a caller-authored string in both
|
|
10
|
-
* fields — and "payload-free by construction" has to mean the event type has
|
|
11
|
-
* nowhere to put a payload, not merely that connecta declines to. A 40 KB
|
|
12
|
-
* invented connector id is a payload wearing an id's clothing.
|
|
13
|
-
*
|
|
14
|
-
* Clamped rather than dropped: the invented id is precisely what an operator
|
|
15
|
-
* needs to see, and its first 128 bytes identify the mistake as well as all
|
|
16
|
-
* 40,000 would. The `…` marker keeps a clamped value from reading as a real one.
|
|
17
|
-
*/
|
|
18
|
-
const MAX_ACTIVITY_NAME_BYTES = 128;
|
|
19
|
-
|
|
20
|
-
/** Two names and the dot between them. */
|
|
21
|
-
const MAX_ACTIVITY_ADDRESS_BYTES = MAX_ACTIVITY_NAME_BYTES * 2 + 1;
|
|
22
|
-
|
|
23
|
-
export type ActivityCallSource =
|
|
24
|
-
| "call_tool"
|
|
25
|
-
| "call_destructive_tool"
|
|
26
|
-
// Read-only history. Nothing emits `batch_call` since issue #273 removed the
|
|
27
|
-
// tool, but activity storage is append-only: rows written by older
|
|
28
|
-
// deployments are still read back — the D1 example maps a stored row straight
|
|
29
|
-
// into this type — and an operator's timeline should not have to lie about
|
|
30
|
-
// where a call came from. Never widen this member back into a live source.
|
|
31
|
-
| "batch_call"
|
|
32
|
-
| "execute_code";
|
|
33
|
-
|
|
34
|
-
export type ActivityOutcome =
|
|
35
|
-
| "success"
|
|
36
|
-
| "error"
|
|
37
|
-
| "timeout"
|
|
38
|
-
| "cancelled";
|
|
39
|
-
|
|
40
|
-
export type AgentFriction =
|
|
41
|
-
| "tool_not_found"
|
|
42
|
-
| "schema_retry"
|
|
43
|
-
| "destructive_reroute"
|
|
44
|
-
| "auth_required"
|
|
45
|
-
| "result_too_large";
|
|
46
|
-
|
|
47
|
-
/** Coarse recovery class derived without inspecting payloads or error prose. */
|
|
48
|
-
export function agentFrictionForCode(
|
|
49
|
-
code: string | undefined,
|
|
50
|
-
): AgentFriction | undefined {
|
|
51
|
-
switch (code) {
|
|
52
|
-
case "unknown_address":
|
|
53
|
-
case "unknown_tool":
|
|
54
|
-
case "ambiguous_tool_alias":
|
|
55
|
-
return "tool_not_found";
|
|
56
|
-
case "invalid_args":
|
|
57
|
-
return "schema_retry";
|
|
58
|
-
case "destructive_tool_requires_approval":
|
|
59
|
-
return "destructive_reroute";
|
|
60
|
-
case "auth_required":
|
|
61
|
-
return "auth_required";
|
|
62
|
-
case "result_too_large":
|
|
63
|
-
return "result_too_large";
|
|
64
|
-
default:
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Authenticated identity attached to an activity event. `id` is intentionally
|
|
71
|
-
* optional: open deployments and shared bearer tokens cannot honestly identify
|
|
72
|
-
* a person.
|
|
73
|
-
*/
|
|
74
|
-
export interface ActivityActor {
|
|
75
|
-
kind: string;
|
|
76
|
-
id?: string;
|
|
77
|
-
/**
|
|
78
|
-
* Stable, non-secret identity-directory namespace supplied by the admitting
|
|
79
|
-
* auth provider. It lets authorized reads resolve ids through the same
|
|
80
|
-
* provider rather than another provider that happens to share `kind`.
|
|
81
|
-
*/
|
|
82
|
-
namespace?: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Privacy-minimal history of one resolved downstream connector call.
|
|
87
|
-
*
|
|
88
|
-
* Arguments, results, generated code, search text, and raw errors are excluded
|
|
89
|
-
* by construction. Deployments that need a safe human summary can add a
|
|
90
|
-
* separate, explicit connector-level feature later.
|
|
91
|
-
*/
|
|
92
|
-
export interface ToolCallActivityEvent {
|
|
93
|
-
schemaVersion: 1;
|
|
94
|
-
id: string;
|
|
95
|
-
occurredAt: string;
|
|
96
|
-
requestId: string;
|
|
97
|
-
actor: ActivityActor;
|
|
98
|
-
connectorId: string;
|
|
99
|
-
toolName: string;
|
|
100
|
-
address: string;
|
|
101
|
-
source: ActivityCallSource;
|
|
102
|
-
outcome: ActivityOutcome;
|
|
103
|
-
durationMs: number;
|
|
104
|
-
attempts: number;
|
|
105
|
-
/** Set only when the call actually failed; a truncated success has none. */
|
|
106
|
-
errorCode?: string;
|
|
107
|
-
/**
|
|
108
|
-
* Payload-free recovery class. Usually derived from `errorCode`, but it can
|
|
109
|
-
* also stand alone: a result too large to return inline is friction for the
|
|
110
|
-
* agent while remaining an `outcome: "success"` call with no error code.
|
|
111
|
-
*/
|
|
112
|
-
friction?: AgentFriction;
|
|
113
|
-
serverName: string;
|
|
114
|
-
serverVersion: string;
|
|
115
|
-
deploymentId?: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export interface ActivityPage {
|
|
119
|
-
events: ToolCallActivityEvent[];
|
|
120
|
-
nextCursor?: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** Display-only actor returned by the authenticated activity read API. */
|
|
124
|
-
export interface ActivityReadActor extends ActivityActor {
|
|
125
|
-
label?: string;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export type ActivityReadEvent = Omit<ToolCallActivityEvent, "actor"> & {
|
|
129
|
-
actor: ActivityReadActor;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export interface ActivityReadPage {
|
|
133
|
-
events: ActivityReadEvent[];
|
|
134
|
-
nextCursor?: string;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Write-only deployments can implement only this small, vendor-neutral seam. */
|
|
138
|
-
export interface ActivitySink {
|
|
139
|
-
record(event: ToolCallActivityEvent): void | Promise<void>;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** Optional read side used by Connecta's authenticated Activity UI. */
|
|
143
|
-
export interface ActivityReader {
|
|
144
|
-
list(options: { cursor?: string; limit: number }): Promise<ActivityPage>;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface ActivityStore extends ActivitySink {
|
|
148
|
-
list?: ActivityReader["list"];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Reader implementations throw this for an opaque cursor they cannot decode. */
|
|
152
|
-
export class InvalidActivityCursorError extends Error {
|
|
153
|
-
override name = "InvalidActivityCursorError";
|
|
154
|
-
|
|
155
|
-
constructor() {
|
|
156
|
-
super("invalid activity cursor");
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export type ActivityReadGate = (
|
|
161
|
-
actor: ActivityActor,
|
|
162
|
-
) => boolean | Promise<boolean>;
|
|
163
|
-
|
|
164
|
-
/** Request-scoped context shared by direct, batch, and code-mode call paths. */
|
|
165
|
-
export interface ActivityRequestContext {
|
|
166
|
-
sink: ActivitySink;
|
|
167
|
-
actor: ActivityActor;
|
|
168
|
-
requestId: string;
|
|
169
|
-
serverInfo: { name: string; version: string };
|
|
170
|
-
deploymentId?: string;
|
|
171
|
-
defer?: (promise: Promise<unknown>) => void;
|
|
172
|
-
logger: Logger;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export type ActivityEventInput = Pick<
|
|
176
|
-
ToolCallActivityEvent,
|
|
177
|
-
| "connectorId"
|
|
178
|
-
| "toolName"
|
|
179
|
-
| "address"
|
|
180
|
-
| "source"
|
|
181
|
-
| "outcome"
|
|
182
|
-
| "durationMs"
|
|
183
|
-
| "attempts"
|
|
184
|
-
| "errorCode"
|
|
185
|
-
| "friction"
|
|
186
|
-
>;
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Best-effort by design: activity storage can never change a tool result.
|
|
190
|
-
* Workers attach async sinks to waitUntil; synchronous sinks such as Analytics
|
|
191
|
-
* Engine complete inline; Node promises remain detached from the response.
|
|
192
|
-
*/
|
|
193
|
-
export function recordToolActivity(
|
|
194
|
-
context: ActivityRequestContext | undefined,
|
|
195
|
-
input: ActivityEventInput,
|
|
196
|
-
): void {
|
|
197
|
-
if (!context) return;
|
|
198
|
-
// A caller-supplied class wins because it knows something the code table
|
|
199
|
-
// cannot: friction that belongs to a call which did not fail.
|
|
200
|
-
const friction = input.friction ?? agentFrictionForCode(input.errorCode);
|
|
201
|
-
const event: ToolCallActivityEvent = {
|
|
202
|
-
schemaVersion: 1,
|
|
203
|
-
id: crypto.randomUUID(),
|
|
204
|
-
occurredAt: new Date().toISOString(),
|
|
205
|
-
requestId: context.requestId,
|
|
206
|
-
actor: context.actor,
|
|
207
|
-
connectorId: boundedEchoText(input.connectorId, MAX_ACTIVITY_NAME_BYTES),
|
|
208
|
-
toolName: boundedEchoText(input.toolName, MAX_ACTIVITY_NAME_BYTES),
|
|
209
|
-
address: boundedEchoText(input.address, MAX_ACTIVITY_ADDRESS_BYTES),
|
|
210
|
-
source: input.source,
|
|
211
|
-
outcome: input.outcome,
|
|
212
|
-
durationMs: Math.max(0, Math.trunc(input.durationMs)),
|
|
213
|
-
attempts: Math.max(1, Math.trunc(input.attempts)),
|
|
214
|
-
...(input.errorCode ? { errorCode: input.errorCode } : {}),
|
|
215
|
-
...(friction ? { friction } : {}),
|
|
216
|
-
serverName: context.serverInfo.name,
|
|
217
|
-
serverVersion: context.serverInfo.version,
|
|
218
|
-
...(context.deploymentId
|
|
219
|
-
? { deploymentId: context.deploymentId }
|
|
220
|
-
: {}),
|
|
221
|
-
};
|
|
222
|
-
try {
|
|
223
|
-
const result = context.sink.record(event);
|
|
224
|
-
if (!result || typeof (result as Promise<unknown>).then !== "function") {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
const pending = Promise.resolve(result).catch((error) => {
|
|
228
|
-
context.logger.warn("[connecta] activity record failed", error);
|
|
229
|
-
});
|
|
230
|
-
if (context.defer) context.defer(pending);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
context.logger.warn("[connecta] activity record failed", error);
|
|
233
|
-
}
|
|
234
|
-
}
|