@desplega.ai/agent-swarm 2.4.4 → 2.4.5
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/dist/{acp-adapter-thq04adh.js → acp-adapter-2wdkktv6.js} +1 -1
- package/dist/{actions-j6ywh6x5.js → actions-c40ebcm4.js} +18 -18
- package/dist/{amp-adapter-k8b5xmj8.js → amp-adapter-wb67m95z.js} +2 -2
- package/dist/{app-rb7vc02s.js → app-353vre8g.js} +3 -3
- package/dist/{assistant-ftta1zjf.js → assistant-sj1njh23.js} +21 -21
- package/dist/{boot-reembed-srp6v6af.js → boot-reembed-b9c3az3c.js} +1 -1
- package/dist/{boot-reembed-xns7h5h9.js → boot-reembed-qdkvxeem.js} +1 -1
- package/dist/{boot-scrub-sweep-3qqrh2mz.js → boot-scrub-sweep-m3wh8eks.js} +2 -2
- package/dist/{boot-seeds-9ks8hce7.js → boot-seeds-j2273s54.js} +4 -4
- package/dist/{claude-adapter-k5ksyres.js → claude-adapter-m7q0a085.js} +2 -2
- package/dist/{claude-managed-adapter-h2y889ge.js → claude-managed-adapter-5ptb6tzs.js} +2 -2
- package/dist/{claude-managed-setup-n5a5eqks.js → claude-managed-setup-smrqk5j5.js} +3 -3
- package/dist/{claude-sdk-session-2j3j4kwn.js → claude-sdk-session-38xnz21e.js} +8284 -8267
- package/dist/{cli-cynvc9fj.js → cli-018r4jq6.js} +5 -5
- package/dist/{cli-9fhkkpj5.js → cli-1f3qzw6y.js} +1 -1
- package/dist/{cli-dqb4n629.js → cli-1sp861s4.js} +7 -7
- package/dist/{cli-gqvh8d8a.js → cli-1syn2xy1.js} +10 -10
- package/dist/{cli-qkhc5aqy.js → cli-37j04k05.js} +11 -11
- package/dist/{cli-1d70crsy.js → cli-3a45zcgm.js} +1 -1
- package/dist/{cli-t7cyef5g.js → cli-43x64hv3.js} +4 -4
- package/dist/{cli-hj3debgw.js → cli-4zfqgjc5.js} +2 -2
- package/dist/{cli-55qmbp6z.js → cli-7c9qs530.js} +1 -1
- package/dist/{cli-9qtjfd60.js → cli-848hxdwj.js} +1 -1
- package/dist/{cli-pccfmtb5.js → cli-94gwyg7m.js} +7 -7
- package/dist/{cli-51y09c6p.js → cli-9hstj09h.js} +5 -5
- package/dist/{cli-zrwpctp4.js → cli-9yqgm9nd.js} +22 -22
- package/dist/{cli-esxerzg7.js → cli-a92r1127.js} +1 -1
- package/dist/{cli-b8k7epj1.js → cli-be2161h1.js} +7 -7
- package/dist/{cli-xc27fwzg.js → cli-bnes4z6b.js} +2 -2
- package/dist/{cli-zrk6jxv8.js → cli-cxxe61f6.js} +1 -1
- package/dist/{cli-amyhm3gj.js → cli-dk44dxec.js} +1 -1
- package/dist/{cli-nc35pxb7.js → cli-eww120g8.js} +1 -0
- package/dist/{cli-a0kcty51.js → cli-f01h25yt.js} +1 -1
- package/dist/{cli-82d3nkc3.js → cli-f0g3pgvf.js} +1 -1
- package/dist/{cli-1ephgwnq.js → cli-ftc1g5tj.js} +2 -2
- package/dist/{cli-2v045xyf.js → cli-fv5hepyy.js} +3 -3
- package/dist/{cli-bnz0r3kc.js → cli-g35m2xwm.js} +4 -4
- package/dist/{cli-qje9k2nh.js → cli-g9ypd5en.js} +12 -2
- package/dist/{cli-w6nadkh4.js → cli-j99azg3g.js} +3 -3
- package/dist/{cli-er83cjbn.js → cli-k8g16gz3.js} +416 -116
- package/dist/{cli-4dg55wsk.js → cli-kpw04va0.js} +1 -1
- package/dist/{cli-3cgngyd8.js → cli-ms85309d.js} +33 -15
- package/dist/{cli-v3yr2gbg.js → cli-n72rg53p.js} +1471 -635
- package/dist/{cli-37yb7307.js → cli-p14m2cnp.js} +1 -1
- package/dist/{cli-2zqpkvtt.js → cli-qfxc84gh.js} +1 -1
- package/dist/{cli-44159k28.js → cli-qwhy2qma.js} +1 -1
- package/dist/{cli-2y91wnbh.js → cli-st8aw1m6.js} +221 -9
- package/dist/{cli-vq97g1jq.js → cli-tw4p90jb.js} +92 -192
- package/dist/{cli-te6b5wvg.js → cli-v0yrfmdh.js} +1 -1
- package/dist/{cli-xj0re5tm.js → cli-v8fjakwf.js} +1 -1
- package/dist/{cli-p20r8w42.js → cli-wt0jf9hm.js} +215 -80
- package/dist/{cli-q6r7t299.js → cli-xpa64rek.js} +33 -11
- package/dist/{cli-vdm20wej.js → cli-z7d29rw9.js} +2 -2
- package/dist/cli.js +19 -19
- package/dist/{codex-adapter-968xwfy7.js → codex-adapter-tn6k5mkg.js} +1 -1
- package/dist/{codex-session-runner-ht4z99fa.js → codex-session-runner-7vjt7jbw.js} +1 -1
- package/dist/{commands-pcymaeg9.js → commands-t7rz9146.js} +2 -2
- package/dist/{controller-fpjyk46r.js → controller-m2jasma5.js} +2 -2
- package/dist/{controllers-connect-mwzx1fh6.js → controllers-connect-9ftpex99.js} +19 -19
- package/dist/{cursor-adapter-cp4s2e2f.js → cursor-adapter-mjv79e4z.js} +1 -1
- package/dist/{db-xsjg9jqq.js → db-6sfrn9g3.js} +1 -1
- package/dist/{db-sgd12bt9.js → db-rn673r06.js} +3 -3
- package/dist/{dispatcher-x0jfw2c9.js → dispatcher-c155xz0p.js} +17 -17
- package/dist/{dsh-adapter-jrq7ma7h.js → dsh-adapter-3mmszc1x.js} +1 -1
- package/dist/{external-channel-8njj4h3b.js → external-channel-35k6tkv8.js} +2 -2
- package/dist/{grok-adapter-kkr97v2f.js → grok-adapter-tfrq7tg1.js} +2 -2
- package/dist/handlers-7gw64dxw.js +39 -0
- package/dist/{heartbeat-wd03zhn0.js → heartbeat-dyg4npkp.js} +20 -20
- package/dist/{hook-f0vghj42.js → hook-drspbwpe.js} +1 -1
- package/dist/{hook-xzj2ts2p.js → hook-x26b31bg.js} +1 -1
- package/dist/{http-4hwxv62p.js → http-pk972xyw.js} +355 -216
- package/dist/{index-ww2vznkp.js → index-arytgj5q.js} +1 -1
- package/dist/{index-hg0x2cdv.js → index-bgdyvcnv.js} +17 -17
- package/dist/{index-c4ykd0f1.js → index-dr94zbnh.js} +13348 -11611
- package/dist/{index-gdh8jrdm.js → index-gabbcdn2.js} +17 -17
- package/dist/{index-97q4xn03.js → index-jwxkkex2.js} +129 -75
- package/dist/index-tfb26zhy.js +41 -0
- package/dist/{keepalive-hrh7xnr9.js → keepalive-7yrng7px.js} +3 -3
- package/dist/{lead-ee8vazz2.js → lead-7e02z839.js} +29 -29
- package/dist/{maintenance-e8wnwnqx.js → maintenance-e6cemdqt.js} +1 -1
- package/dist/{node-3v3f08f7.js → node-de6akczr.js} +10 -9
- package/dist/{oauth-refresh-sweep-zm0yj4sj.js → oauth-refresh-sweep-yw3xdbcj.js} +2 -2
- package/dist/{onboard-dtwcq603.js → onboard-0a9y5yrc.js} +2 -2
- package/dist/{otel-impl-w3fbhpn0.js → otel-impl-trjpkfb7.js} +1 -1
- package/dist/{pricing-refresh-j38dehfs.js → pricing-refresh-d2b5my0y.js} +3 -3
- package/dist/{rbac-roles-ejxx9b35.js → rbac-roles-hey3yv9d.js} +2 -2
- package/dist/{rbac-roles-ae8yttq4.js → rbac-roles-nxh5ahwk.js} +1 -1
- package/dist/{registry-rxw10xzg.js → registry-ajhexe44.js} +1 -1
- package/dist/{render-v2-n16n0phe.js → render-v2-rr3p2atg.js} +3 -3
- package/dist/{resume-z0fgjc38.js → resume-2efkf1s1.js} +5 -5
- package/dist/{session-access-cv3mq6mc.js → session-access-s2mttxpw.js} +4 -4
- package/dist/{setup-mafhj3yt.js → setup-vypma5yt.js} +1 -1
- package/dist/{work-objects-8b86mgjp.js → work-objects-rvhckdkp.js} +20 -20
- package/dist/{worker-16s75hm5.js → worker-5snytpbd.js} +29 -29
- package/openapi.json +9 -2
- package/package.json +7 -7
- package/src/apps/catalog.generated.json +206 -4
- package/src/apps/page-validator.ts +13 -1
- package/src/be/chunking.ts +42 -9
- package/src/be/db/tasks/draft-text.ts +15 -0
- package/src/be/db/tasks/read.ts +14 -1
- package/src/be/db.ts +5 -1
- package/src/be/memory/index-content.ts +36 -17
- package/src/be/memory/providers/openai-embedding.ts +115 -61
- package/src/be/memory/providers/sqlite-store.ts +69 -23
- package/src/be/memory/reranker.ts +31 -1
- package/src/be/memory/search-candidates.ts +47 -0
- package/src/be/memory/types.ts +14 -0
- package/src/be/scripts/typecheck.ts +2 -0
- package/src/be/seed-scripts/catalog/heartbeat-jev-gate.ts +106 -54
- package/src/be/seed-scripts/index.ts +2 -2
- package/src/be/swarm-config-guard.ts +24 -1
- package/src/be/task-attachment-store.ts +30 -0
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +7 -0
- package/src/fs/provider.ts +13 -1
- package/src/heartbeat/heartbeat.ts +31 -11
- package/src/http/fs.ts +54 -34
- package/src/http/mcp-body.ts +37 -0
- package/src/http/mcp-user.ts +3 -5
- package/src/http/mcp.ts +6 -7
- package/src/http/memory.ts +26 -15
- package/src/http/page-proxy.ts +61 -5
- package/src/http/pages-public.ts +27 -44
- package/src/http/pages.ts +4 -4
- package/src/http/script-runs.ts +2 -0
- package/src/http/users.ts +12 -1
- package/src/integrations/chat-sdk/adapters.ts +7 -3
- package/src/integrations/chat-sdk/attachments.ts +90 -12
- package/src/integrations/chat-sdk/facts.ts +120 -0
- package/src/integrations/chat-sdk/inbound.ts +156 -8
- package/src/integrations/chat-sdk/limits.ts +24 -0
- package/src/integrations/chat-sdk/outbound-files.ts +186 -0
- package/src/integrations/chat-sdk/outbound.ts +234 -4
- package/src/integrations/chat-sdk/relay.ts +4 -1
- package/src/integrations/chat-sdk/router.ts +28 -2
- package/src/integrations/chat-sdk/steering.ts +9 -0
- package/src/integrations/chat-sdk/templates.ts +14 -0
- package/src/integrations/chat-sdk/thread-buffer.ts +4 -0
- package/src/mcp-limits.ts +20 -0
- package/src/pages/browser-sdk.ts +8 -3
- package/src/pages/feedback-overlay.ts +1 -1
- package/src/providers/amp-adapter.ts +1 -1
- package/src/providers/codex-adapter.ts +106 -119
- package/src/providers/codex-agents-md.ts +35 -229
- package/src/providers/cursor-adapter.ts +1 -1
- package/src/realtime/transport.ts +1 -2
- package/src/script-workflows/workflow-ctx.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +2 -0
- package/src/scripts-runtime/types/swarm-extension.d.ts +8 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +2 -0
- package/src/server-user.ts +2 -0
- package/src/server.ts +2 -0
- package/src/slack/assistant.ts +2 -2
- package/src/slack/files.ts +2 -0
- package/src/slack/handlers.ts +4 -4
- package/src/slack/inbound-files.ts +39 -6
- package/src/slack/thread-buffer.ts +2 -1
- package/src/tasks/task-terminal-effects.ts +21 -17
- package/src/tests/apps-card-grid.test.ts +197 -0
- package/src/tests/apps-catalog-additions.test.ts +120 -0
- package/src/tests/apps-tree.test.ts +129 -0
- package/src/tests/chat-sdk-attachments-reply.test.ts +52 -0
- package/src/tests/chat-sdk-config.test.ts +11 -0
- package/src/tests/chat-sdk-facts.test.ts +23 -0
- package/src/tests/chat-sdk-inbound.test.ts +558 -4
- package/src/tests/chat-sdk-outbound-files.test.ts +652 -0
- package/src/tests/codex-adapter.test.ts +167 -242
- package/src/tests/config-api-only-keys.test.ts +9 -0
- package/src/tests/cors-allowlist.test.ts +1 -1
- package/src/tests/extensions-example-slack-reply-gate.test.ts +262 -91
- package/src/tests/heartbeat-checklist.test.ts +33 -0
- package/src/tests/heartbeat-jev-gate-credentials.test.ts +44 -0
- package/src/tests/heartbeat-jev-gate.test.ts +242 -1
- package/src/tests/kv-page-proxy.test.ts +91 -5
- package/src/tests/launch-password-rejection.test.ts +2 -2
- package/src/tests/mcp-input-caps.test.ts +294 -0
- package/src/tests/memory-chunked-search-starvation.test.ts +200 -0
- package/src/tests/memory-embedding-input-cap.test.ts +114 -0
- package/src/tests/memory.test.ts +14 -0
- package/src/tests/page-guest-session.test.ts +19 -8
- package/src/tests/page-proxy-authed.test.ts +1 -1
- package/src/tests/page-proxy.test.ts +17 -10
- package/src/tests/page-session.test.ts +102 -1
- package/src/tests/pages-authed-mode.test.ts +82 -12
- package/src/tests/pages-password-mode.test.ts +5 -5
- package/src/tests/realtime-transport.test.ts +13 -7
- package/src/tests/script-runs-http.test.ts +84 -0
- package/src/tests/scripts-http.test.ts +10 -1
- package/src/tests/swarm-config-export-to-env.test.ts +22 -3
- package/src/tests/task-audio-transcription.test.ts +299 -0
- package/src/tests/task-completion-memory-chunking.test.ts +434 -0
- package/src/tests/task-terminal-secret-scrub.test.ts +6 -1
- package/src/tests/transcription.test.ts +299 -0
- package/src/tests/workflow-system-one-decision-providers.test.ts +98 -1
- package/src/tests/workflow-system-one-decision.test.ts +2 -1
- package/src/tools/chat-access.ts +10 -0
- package/src/tools/chat-post.ts +31 -11
- package/src/tools/chat-reply.ts +32 -10
- package/src/tools/memory-search.ts +3 -1
- package/src/tools/system-one-decide.ts +17 -4
- package/src/types.ts +6 -0
- package/src/utils/config-env-export.ts +3 -1
- package/src/utils/page-session.ts +133 -20
- package/src/utils/transcription.ts +246 -0
- package/src/workflows/executors/system-one-decision.ts +28 -7
- package/src/workflows/executors/system-one-providers.ts +39 -4
- package/templates/extensions/slack-reply-gate/README.md +24 -22
- package/templates/extensions/slack-reply-gate/hooks.ts +146 -95
- package/templates/extensions/slack-reply-gate/manifest.json +2 -2
- package/templates/skills/apps/SKILL.md +28 -6
- package/templates/skills/apps/content.md +28 -6
- package/templates/skills/chat-sdk/content.md +6 -2
- package/templates/skills/code-quality/content.md +2 -1
- package/templates/skills/heartbeat-runbook/content.md +5 -5
- package/templates/skills/pages/content.md +6 -4
- package/templates/skills/swarm-scripts/SKILL.md +2 -2
- package/templates/skills/swarm-scripts/content.md +2 -2
- package/dist/handlers-0pvee9nw.js +0 -39
- package/dist/index-thm0y6a8.js +0 -41
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { closeDb, createAgent, getDbClient, initDb, upsertSwarmConfig } from "../be/db";
|
|
2
4
|
import * as contractRuntime from "../extensions/contract-runtime";
|
|
5
|
+
import { registerSystemOneDecideTool } from "../tools/system-one-decide";
|
|
3
6
|
|
|
4
|
-
// Unit tests for templates/extensions/slack-reply-gate/hooks.ts.
|
|
5
|
-
//
|
|
7
|
+
// Unit tests for templates/extensions/slack-reply-gate/hooks.ts. Decisions go to a
|
|
8
|
+
// stubbed `ctx.swarm.systemOne_decide` and Slack calls to a mocked fetch; nothing
|
|
9
|
+
// reaches the network. The last block runs the real system-one-decide tool behind it.
|
|
6
10
|
|
|
7
11
|
// `swarm-extension` is a bare import the extension loader shims to contract-runtime.
|
|
8
12
|
mock.module("swarm-extension", () => contractRuntime);
|
|
@@ -11,11 +15,14 @@ type Handler = (event: any, ctx: any) => Promise<any>;
|
|
|
11
15
|
let handler: Handler;
|
|
12
16
|
let mod: any;
|
|
13
17
|
|
|
14
|
-
// The hook reads
|
|
15
|
-
// runner's values are saved here and restored after the file.
|
|
18
|
+
// The hook reads credential presence and the Slack token from the API process env.
|
|
19
|
+
// Each test sets its own; the runner's values are saved here and restored after the file.
|
|
16
20
|
const ENV_KEYS = [
|
|
17
21
|
"TYPESAFE_API_KEY",
|
|
18
22
|
"OPENROUTER_API_KEY",
|
|
23
|
+
"CLOUDFLARE_API_TOKEN",
|
|
24
|
+
"CLOUDFLARE_API_KEY",
|
|
25
|
+
"CLOUDFLARE_ACCOUNT_ID",
|
|
19
26
|
"SLACK_BOT_TOKEN",
|
|
20
27
|
...["ACCEPTED", "BUFFERED", "NOW", "STEERED"].map((k) => `SLACK_REACTION_${k}`),
|
|
21
28
|
];
|
|
@@ -54,8 +61,25 @@ const slackOk = (method: string, body: any): Response => {
|
|
|
54
61
|
};
|
|
55
62
|
let slackReply: SlackReply = slackOk;
|
|
56
63
|
|
|
57
|
-
/**
|
|
58
|
-
|
|
64
|
+
/** What the MCP bridge returns for a tool call: the tool's structured content in `data`. */
|
|
65
|
+
type BridgeResult = { success: boolean; status: number; data: Record<string, unknown> };
|
|
66
|
+
const served = (data: Record<string, unknown>): BridgeResult => ({
|
|
67
|
+
success: true,
|
|
68
|
+
status: 200,
|
|
69
|
+
data: { success: true, message: "Decision", ...data },
|
|
70
|
+
});
|
|
71
|
+
const refused = (message: string): BridgeResult => ({
|
|
72
|
+
success: true,
|
|
73
|
+
status: 200,
|
|
74
|
+
data: { success: false, message },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
type DecideReply = (args: any) => BridgeResult | Promise<BridgeResult>;
|
|
78
|
+
let decideReply: DecideReply = () => refused("no decision stub");
|
|
79
|
+
|
|
80
|
+
/** Stub `systemOne_decide` with `reply(args)`. Slack calls go to `slackReply` through fetch. */
|
|
81
|
+
const mockJev = (reply: DecideReply) => {
|
|
82
|
+
decideReply = reply;
|
|
59
83
|
globalThis.fetch = (async (url: any, init: any) => {
|
|
60
84
|
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
61
85
|
const href = String(url);
|
|
@@ -64,8 +88,7 @@ const mockJev = (reply: (body: any) => Response | Promise<Response>) => {
|
|
|
64
88
|
slackCalls.push({ method, body, auth: String(init?.headers?.Authorization ?? "") });
|
|
65
89
|
return slackReply(method, body);
|
|
66
90
|
}
|
|
67
|
-
|
|
68
|
-
return reply(body);
|
|
91
|
+
throw new Error(`unexpected fetch: ${href}`);
|
|
69
92
|
}) as typeof fetch;
|
|
70
93
|
};
|
|
71
94
|
const jevChoice = (
|
|
@@ -73,27 +96,29 @@ const jevChoice = (
|
|
|
73
96
|
confidence = 0.9,
|
|
74
97
|
probabilities: Record<string, number> | null = { [choice]: confidence, other: 1 - confidence },
|
|
75
98
|
) =>
|
|
76
|
-
mockJev(
|
|
77
|
-
(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
}),
|
|
90
|
-
{ status: 200 },
|
|
91
|
-
),
|
|
99
|
+
mockJev(() =>
|
|
100
|
+
served({
|
|
101
|
+
model: "jev-1.13.0",
|
|
102
|
+
answers: {
|
|
103
|
+
reason: {
|
|
104
|
+
type: "choice",
|
|
105
|
+
choice,
|
|
106
|
+
confidence,
|
|
107
|
+
...(probabilities ? { probabilities } : {}),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
92
111
|
);
|
|
93
112
|
|
|
94
113
|
const makeCtx = (
|
|
95
114
|
config: Record<string, unknown> = {},
|
|
96
|
-
opts: {
|
|
115
|
+
opts: {
|
|
116
|
+
key?: string;
|
|
117
|
+
openrouterKey?: string;
|
|
118
|
+
cloudflareToken?: string;
|
|
119
|
+
cloudflareAccount?: string;
|
|
120
|
+
slackToken?: string;
|
|
121
|
+
} = {},
|
|
97
122
|
) => {
|
|
98
123
|
const kv: Record<string, unknown> = {};
|
|
99
124
|
const counters: Record<string, number> = {};
|
|
@@ -103,8 +128,14 @@ const makeCtx = (
|
|
|
103
128
|
};
|
|
104
129
|
setEnv("TYPESAFE_API_KEY", opts.key ?? "tsk-test");
|
|
105
130
|
setEnv("OPENROUTER_API_KEY", opts.openrouterKey ?? "");
|
|
131
|
+
setEnv("CLOUDFLARE_API_TOKEN", opts.cloudflareToken ?? "");
|
|
132
|
+
setEnv("CLOUDFLARE_ACCOUNT_ID", opts.cloudflareAccount ?? "");
|
|
106
133
|
setEnv("SLACK_BOT_TOKEN", opts.slackToken ?? "xoxb-test");
|
|
107
|
-
const swarm = {
|
|
134
|
+
const swarm: Record<string, (args: any) => Promise<unknown>> = {
|
|
135
|
+
systemOne_decide: async (args: any) => {
|
|
136
|
+
jevCalls.push({ provider: args.provider, body: args });
|
|
137
|
+
return decideReply(args);
|
|
138
|
+
},
|
|
108
139
|
kv_set: async ({ key, value }: any) => {
|
|
109
140
|
kv[key] = value;
|
|
110
141
|
return { success: true };
|
|
@@ -154,6 +185,7 @@ beforeEach(() => {
|
|
|
154
185
|
jevCalls = [];
|
|
155
186
|
slackCalls = [];
|
|
156
187
|
slackReply = slackOk;
|
|
188
|
+
decideReply = () => refused("no decision stub");
|
|
157
189
|
mod.resetKeyCache();
|
|
158
190
|
});
|
|
159
191
|
afterEach(async () => {
|
|
@@ -325,107 +357,112 @@ describe("decide", () => {
|
|
|
325
357
|
});
|
|
326
358
|
});
|
|
327
359
|
|
|
328
|
-
describe("
|
|
360
|
+
describe("provider", () => {
|
|
329
361
|
const cfg = (extra: Record<string, unknown> = {}) => mod.config.parse(extra);
|
|
362
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
330
363
|
|
|
331
|
-
test("auto prefers TypeSafe when
|
|
332
|
-
makeCtx(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
364
|
+
test("auto prefers TypeSafe when every credential is set", () => {
|
|
365
|
+
makeCtx(
|
|
366
|
+
{},
|
|
367
|
+
{
|
|
368
|
+
key: "tsk-test",
|
|
369
|
+
openrouterKey: "or-test",
|
|
370
|
+
cloudflareToken: "cf",
|
|
371
|
+
cloudflareAccount: CF_ACCOUNT,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
expect(mod.resolveProvider(cfg())).toEqual({ provider: "typesafe", model: "jev-latest" });
|
|
339
375
|
});
|
|
340
376
|
|
|
341
|
-
test("auto falls back to OpenRouter
|
|
342
|
-
makeCtx(
|
|
343
|
-
|
|
377
|
+
test("auto falls back to OpenRouter, then to Clef", () => {
|
|
378
|
+
makeCtx(
|
|
379
|
+
{},
|
|
380
|
+
{ key: "", openrouterKey: "or-test", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT },
|
|
381
|
+
);
|
|
382
|
+
expect(mod.resolveProvider(cfg())).toEqual({
|
|
344
383
|
provider: "openrouter",
|
|
345
|
-
endpoint: "https://openrouter.ai/api/alpha/decisions",
|
|
346
|
-
key: "or-test",
|
|
347
384
|
model: "typesafe/jev-1.13",
|
|
348
385
|
});
|
|
386
|
+
mod.resetKeyCache();
|
|
387
|
+
makeCtx({}, { key: "", cloudflareToken: "cf", cloudflareAccount: CF_ACCOUNT });
|
|
388
|
+
expect(mod.resolveProvider(cfg())).toEqual({ provider: "cloudflare", model: "clef" });
|
|
349
389
|
});
|
|
350
390
|
|
|
351
|
-
test("
|
|
352
|
-
makeCtx({}, { key: "
|
|
353
|
-
|
|
391
|
+
test("auto accepts CLOUDFLARE_API_KEY and needs the account id", () => {
|
|
392
|
+
makeCtx({}, { key: "", cloudflareAccount: CF_ACCOUNT });
|
|
393
|
+
process.env.CLOUDFLARE_API_KEY = "cf-key";
|
|
394
|
+
expect(mod.resolveProvider(cfg()).provider).toBe("cloudflare");
|
|
354
395
|
mod.resetKeyCache();
|
|
355
|
-
makeCtx({}, { key: "",
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
396
|
+
makeCtx({}, { key: "", cloudflareToken: "cf" });
|
|
397
|
+
delete process.env.CLOUDFLARE_API_KEY;
|
|
398
|
+
expect(() => mod.resolveProvider(cfg())).toThrow("CLOUDFLARE_ACCOUNT_ID");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("an explicit provider is used as is; the server checks its credential", () => {
|
|
402
|
+
makeCtx({}, { key: "tsk-test", openrouterKey: "or-test" });
|
|
403
|
+
expect(mod.resolveProvider(cfg({ provider: "openrouter" })).provider).toBe("openrouter");
|
|
404
|
+
expect(
|
|
405
|
+
mod.resolveProvider(cfg({ provider: "cloudflare", cloudflareModel: "clef-flash" })),
|
|
406
|
+
).toEqual({
|
|
407
|
+
provider: "cloudflare",
|
|
408
|
+
model: "clef-flash",
|
|
409
|
+
});
|
|
410
|
+
// A host with no model setting sends none: the server uses the host's default.
|
|
411
|
+
expect(mod.resolveProvider(cfg({ provider: "laya" }))).toEqual({
|
|
412
|
+
provider: "laya",
|
|
413
|
+
model: undefined,
|
|
414
|
+
});
|
|
359
415
|
});
|
|
360
416
|
|
|
361
|
-
test("auto with no
|
|
417
|
+
test("auto with no credential names every secret", () => {
|
|
362
418
|
makeCtx({}, { key: "", openrouterKey: "" });
|
|
363
|
-
expect(() => mod.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
test("an OpenRouter-only swarm classifies through the Decisions endpoint", async () => {
|
|
367
|
-
// Shape captured from a live OpenRouter Decisions call to typesafe/jev-1.13.
|
|
368
|
-
mockJev(() =>
|
|
369
|
-
Response.json({
|
|
370
|
-
model: "typesafe/jev-1.13-20260917",
|
|
371
|
-
answers: {
|
|
372
|
-
reason: {
|
|
373
|
-
type: "choice",
|
|
374
|
-
choice: "reaction_only",
|
|
375
|
-
probabilities: { direct_ask: 0, reaction_only: 0.95, banter_between_others: 0.05 },
|
|
376
|
-
confidence: 0.93,
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
usage: { input_tokens: 430, output_tokens: 53, cost: 0.00001806 },
|
|
380
|
-
id: "gen-dec-1",
|
|
381
|
-
provider: "TypeSafe",
|
|
382
|
-
}),
|
|
419
|
+
expect(() => mod.resolveProvider(cfg())).toThrow(
|
|
420
|
+
"no TYPESAFE_API_KEY, OPENROUTER_API_KEY, or CLOUDFLARE_API_TOKEN with CLOUDFLARE_ACCOUNT_ID",
|
|
383
421
|
);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("the decision goes through systemOne_decide, never a direct fetch", async () => {
|
|
425
|
+
jevChoice("reaction_only", 0.95);
|
|
384
426
|
const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
|
|
385
427
|
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
386
|
-
expect(result).toEqual({ action: "block", reason: expect.stringContaining("
|
|
428
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
387
429
|
expect(jevCalls).toHaveLength(1);
|
|
388
|
-
expect(jevCalls[0].
|
|
389
|
-
|
|
430
|
+
expect(jevCalls[0].body).toMatchObject({
|
|
431
|
+
provider: "openrouter",
|
|
432
|
+
model: "typesafe/jev-1.13",
|
|
433
|
+
timeoutMs: 3000,
|
|
434
|
+
maxRetries: 0,
|
|
435
|
+
});
|
|
390
436
|
expect(Object.keys(jevCalls[0].body.questions.reason.criteria)).toEqual(
|
|
391
437
|
Object.keys(mod.REASONS),
|
|
392
438
|
);
|
|
439
|
+
expect(JSON.stringify(jevCalls[0].body)).not.toContain("or-test");
|
|
393
440
|
expect(kv.last).toMatchObject({ provider: "openrouter", reason: "reaction_only" });
|
|
394
441
|
});
|
|
395
442
|
|
|
396
|
-
test("
|
|
397
|
-
mockJev(() =>
|
|
443
|
+
test("a provider error relayed by the server fails open and names the provider", async () => {
|
|
444
|
+
mockJev(() => refused("OpenRouter returned HTTP 402"));
|
|
398
445
|
const { ctx, kv } = makeCtx({ mode: "enforce" }, { key: "", openrouterKey: "or-test" });
|
|
399
446
|
expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
|
|
400
447
|
expect(kv.last).toMatchObject({
|
|
401
448
|
action: "fail-open",
|
|
402
|
-
error: expect.stringContaining("openrouter 402"),
|
|
449
|
+
error: expect.stringContaining("openrouter: OpenRouter returned HTTP 402"),
|
|
403
450
|
});
|
|
404
451
|
});
|
|
405
452
|
});
|
|
406
453
|
|
|
407
454
|
describe("fail open", () => {
|
|
408
455
|
const cases: Array<[string, () => void, Record<string, unknown>]> = [
|
|
409
|
-
["
|
|
410
|
-
["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
|
|
456
|
+
["provider error", () => mockJev(() => refused("TypeSafe returned HTTP 500")), {}],
|
|
411
457
|
[
|
|
412
|
-
"
|
|
413
|
-
() => mockJev(() =>
|
|
458
|
+
"bridge error",
|
|
459
|
+
() => mockJev(() => ({ success: false, status: 500, data: { error: "boom" } })),
|
|
414
460
|
{},
|
|
415
461
|
],
|
|
462
|
+
["network error", () => mockJev(() => Promise.reject(new Error("ECONNRESET"))), {}],
|
|
463
|
+
["malformed answer", () => mockJev(() => served({ answers: {} })), {}],
|
|
416
464
|
["unknown label", () => jevChoice("definitely_skip"), {}],
|
|
417
|
-
[
|
|
418
|
-
"timeout",
|
|
419
|
-
() =>
|
|
420
|
-
mockJev(
|
|
421
|
-
(_body) =>
|
|
422
|
-
new Promise<Response>((_resolve, reject) => {
|
|
423
|
-
const signal = jevCalls.at(-1)?.signal as AbortSignal;
|
|
424
|
-
signal.addEventListener("abort", () => reject(new Error("aborted")));
|
|
425
|
-
}),
|
|
426
|
-
),
|
|
427
|
-
{ timeoutMs: 50 },
|
|
428
|
-
],
|
|
465
|
+
["timeout", () => mockJev(() => new Promise<BridgeResult>(() => {})), { timeoutMs: 50 }],
|
|
429
466
|
];
|
|
430
467
|
for (const [label, arrange, cfg] of cases) {
|
|
431
468
|
test(`${label} -> continue, error recorded`, async () => {
|
|
@@ -440,7 +477,7 @@ describe("fail open", () => {
|
|
|
440
477
|
});
|
|
441
478
|
}
|
|
442
479
|
|
|
443
|
-
test("missing API key -> continue without
|
|
480
|
+
test("missing API key under auto -> continue without a decision call", async () => {
|
|
444
481
|
jevChoice("reaction_only");
|
|
445
482
|
const { ctx, counters } = makeCtx({ mode: "enforce" }, { key: "" });
|
|
446
483
|
const result = await handler(slackEvent(["lol"]), ctx);
|
|
@@ -754,3 +791,137 @@ describe("mute on drop (enforce)", () => {
|
|
|
754
791
|
expect(slackCalls).toHaveLength(0);
|
|
755
792
|
});
|
|
756
793
|
});
|
|
794
|
+
|
|
795
|
+
describe("on the real system-one-decide tool", () => {
|
|
796
|
+
const CF_ACCOUNT = "0123456789abcdef0123456789abcdef";
|
|
797
|
+
const CF_TOKEN = "cf-reply-gate-example-token";
|
|
798
|
+
const CLEF_URL = `https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT}/ai/run/@cf/cloudflare/clef`;
|
|
799
|
+
let agentId: string;
|
|
800
|
+
let workersAi: Array<{ url: string; authorization: string | null; body: any }> = [];
|
|
801
|
+
|
|
802
|
+
beforeAll(async () => {
|
|
803
|
+
initDb(":memory:");
|
|
804
|
+
agentId = (await createAgent({ name: "reply-gate-ext", isLead: false, status: "idle" })).id;
|
|
805
|
+
await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: CF_ACCOUNT });
|
|
806
|
+
});
|
|
807
|
+
afterAll(() => closeDb());
|
|
808
|
+
|
|
809
|
+
/** `ctx.swarm.systemOne_decide` as the MCP bridge serves it, backed by the registered tool. */
|
|
810
|
+
const viaTool = (ctx: any) => {
|
|
811
|
+
const server = new McpServer({ name: "reply-gate-decide", version: "1" });
|
|
812
|
+
registerSystemOneDecideTool(server);
|
|
813
|
+
const tool = (server as any)._registeredTools["system-one-decide"];
|
|
814
|
+
ctx.swarm.systemOne_decide = async (args: any) => {
|
|
815
|
+
jevCalls.push({ provider: args.provider, body: args });
|
|
816
|
+
const result = await tool.handler(args, {
|
|
817
|
+
sessionId: "extension",
|
|
818
|
+
requestInfo: { headers: { "x-agent-id": agentId } },
|
|
819
|
+
});
|
|
820
|
+
return { success: true, status: 200, data: result.structuredContent };
|
|
821
|
+
};
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
/** Workers AI answering in its REST envelope; Slack calls go to `slackReply`. */
|
|
825
|
+
const mockWorkersAi = (choice: string, p: number) => {
|
|
826
|
+
workersAi = [];
|
|
827
|
+
globalThis.fetch = (async (input: any, init: any) => {
|
|
828
|
+
const url = String(input);
|
|
829
|
+
const body = JSON.parse(String(init?.body ?? "{}"));
|
|
830
|
+
if (url.startsWith("https://slack.com/api/")) {
|
|
831
|
+
const method = url.slice("https://slack.com/api/".length);
|
|
832
|
+
slackCalls.push({ method, body, auth: String(init?.headers?.authorization ?? "") });
|
|
833
|
+
return slackReply(method, body);
|
|
834
|
+
}
|
|
835
|
+
workersAi.push({ url, authorization: new Headers(init?.headers).get("authorization"), body });
|
|
836
|
+
const labels = Object.keys(mod.REASONS);
|
|
837
|
+
const rest = (1 - p) / (labels.length - 1);
|
|
838
|
+
const probabilities = Object.fromEntries(labels.map((l) => [l, l === choice ? p : rest]));
|
|
839
|
+
return Response.json({
|
|
840
|
+
result: {
|
|
841
|
+
model: "clef",
|
|
842
|
+
answers: { reason: { type: "choice", choice, probabilities, confidence: p } },
|
|
843
|
+
usage: { input_tokens: 412, output_tokens: 0 },
|
|
844
|
+
},
|
|
845
|
+
success: true,
|
|
846
|
+
errors: [],
|
|
847
|
+
messages: [],
|
|
848
|
+
});
|
|
849
|
+
}) as typeof fetch;
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
test("Clef classifies on the server; the hook never holds the token", async () => {
|
|
853
|
+
await upsertSwarmConfig({
|
|
854
|
+
scope: "global",
|
|
855
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
856
|
+
value: CF_TOKEN,
|
|
857
|
+
isSecret: true,
|
|
858
|
+
});
|
|
859
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
860
|
+
const { ctx, kv } = makeCtx(
|
|
861
|
+
{ mode: "enforce" },
|
|
862
|
+
{ key: "", cloudflareToken: "present", cloudflareAccount: CF_ACCOUNT },
|
|
863
|
+
);
|
|
864
|
+
viaTool(ctx);
|
|
865
|
+
|
|
866
|
+
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
867
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
868
|
+
expect(workersAi).toHaveLength(1);
|
|
869
|
+
expect(workersAi[0]).toMatchObject({ url: CLEF_URL, authorization: `Bearer ${CF_TOKEN}` });
|
|
870
|
+
expect(workersAi[0]!.body.model).toBe("clef");
|
|
871
|
+
expect(kv.last).toMatchObject({
|
|
872
|
+
provider: "cloudflare",
|
|
873
|
+
reason: "reaction_only",
|
|
874
|
+
action: "skipped",
|
|
875
|
+
});
|
|
876
|
+
expect(JSON.stringify(kv)).not.toContain(CF_TOKEN);
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
test("auto skips OpenRouter behind a gateway, which the server refuses, and reaches Clef", async () => {
|
|
880
|
+
await upsertSwarmConfig({
|
|
881
|
+
scope: "global",
|
|
882
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
883
|
+
value: CF_TOKEN,
|
|
884
|
+
isSecret: true,
|
|
885
|
+
});
|
|
886
|
+
const prevBase = process.env.OPENROUTER_BASE_URL;
|
|
887
|
+
process.env.OPENROUTER_BASE_URL = "https://llm-gateway.example.com/v1";
|
|
888
|
+
try {
|
|
889
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
890
|
+
const { ctx, kv } = makeCtx(
|
|
891
|
+
{ mode: "enforce" },
|
|
892
|
+
{
|
|
893
|
+
key: "",
|
|
894
|
+
openrouterKey: "or-gateway-token",
|
|
895
|
+
cloudflareToken: "present",
|
|
896
|
+
cloudflareAccount: CF_ACCOUNT,
|
|
897
|
+
},
|
|
898
|
+
);
|
|
899
|
+
viaTool(ctx);
|
|
900
|
+
|
|
901
|
+
const result = await handler(slackEvent(["lol nice"]), ctx);
|
|
902
|
+
expect(result).toEqual({ action: "block", reason: expect.stringContaining("reaction_only") });
|
|
903
|
+
expect(jevCalls.map((c) => c.provider)).toEqual(["openrouter", "cloudflare"]);
|
|
904
|
+
expect(workersAi).toHaveLength(1);
|
|
905
|
+
expect(workersAi[0]!.url).toBe(CLEF_URL);
|
|
906
|
+
expect(kv.last).toMatchObject({ provider: "cloudflare", action: "skipped" });
|
|
907
|
+
} finally {
|
|
908
|
+
if (prevBase === undefined) delete process.env.OPENROUTER_BASE_URL;
|
|
909
|
+
else process.env.OPENROUTER_BASE_URL = prevBase;
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test("a token missing on the server fails open, as before", async () => {
|
|
914
|
+
await getDbClient().run("DELETE FROM swarm_config WHERE key = 'CLOUDFLARE_API_TOKEN'");
|
|
915
|
+
mockWorkersAi("reaction_only", 0.9);
|
|
916
|
+
const { ctx, kv, counters } = makeCtx({ mode: "enforce", provider: "cloudflare" }, { key: "" });
|
|
917
|
+
viaTool(ctx);
|
|
918
|
+
|
|
919
|
+
expect(await handler(slackEvent(["lol"]), ctx)).toBeUndefined();
|
|
920
|
+
expect(workersAi).toHaveLength(0);
|
|
921
|
+
expect(counters.error).toBe(1);
|
|
922
|
+
expect(kv.last).toMatchObject({
|
|
923
|
+
action: "fail-open",
|
|
924
|
+
error: expect.stringContaining("CLOUDFLARE_API_TOKEN is not configured"),
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
});
|
|
@@ -532,6 +532,39 @@ describe("Heartbeat Checklist", () => {
|
|
|
532
532
|
expect(calls.map((call) => call.scriptName)).toEqual(["heartbeat-jev-gate"]);
|
|
533
533
|
});
|
|
534
534
|
|
|
535
|
+
test("unset script with a Cloudflare token and account id runs heartbeat-jev-gate", async () => {
|
|
536
|
+
await setGlobal("CLOUDFLARE_API_TOKEN", "cf-test-token");
|
|
537
|
+
await setGlobal("CLOUDFLARE_ACCOUNT_ID", "0123456789abcdef0123456789abcdef");
|
|
538
|
+
useGate({ quiet: true });
|
|
539
|
+
await seedLead();
|
|
540
|
+
|
|
541
|
+
await checkHeartbeatChecklist();
|
|
542
|
+
|
|
543
|
+
expect(calls.map((call) => call.scriptName)).toEqual(["heartbeat-jev-gate"]);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("CLOUDFLARE_API_KEY counts as the Cloudflare token", async () => {
|
|
547
|
+
await setGlobal("CLOUDFLARE_API_KEY", "cf-test-key");
|
|
548
|
+
await setGlobal("CLOUDFLARE_ACCOUNT_ID", "0123456789abcdef0123456789abcdef");
|
|
549
|
+
process.env.HEARTBEAT_JEV_GATE_PROVIDER = "cloudflare";
|
|
550
|
+
useGate({ quiet: true });
|
|
551
|
+
await seedLead();
|
|
552
|
+
|
|
553
|
+
await checkHeartbeatChecklist();
|
|
554
|
+
|
|
555
|
+
expect(calls.map((call) => call.scriptName)).toEqual(["heartbeat-jev-gate"]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("a Cloudflare token without an account id leaves the gate off", async () => {
|
|
559
|
+
await setGlobal("CLOUDFLARE_API_TOKEN", "cf-test-token");
|
|
560
|
+
useGate({ quiet: true });
|
|
561
|
+
await seedLead();
|
|
562
|
+
|
|
563
|
+
await checkHeartbeatChecklist();
|
|
564
|
+
|
|
565
|
+
expect(calls).toHaveLength(0);
|
|
566
|
+
});
|
|
567
|
+
|
|
535
568
|
test("unset script and no Jev key: gate stays off", async () => {
|
|
536
569
|
await setGlobal("ANTHROPIC_API_KEY", "unrelated");
|
|
537
570
|
useGate({ quiet: true });
|
|
@@ -165,4 +165,48 @@ describe("system-one-decide keeps the key on the server", () => {
|
|
|
165
165
|
]);
|
|
166
166
|
expect(JSON.stringify(result)).not.toContain(TYPESAFE_KEY);
|
|
167
167
|
});
|
|
168
|
+
|
|
169
|
+
test("with no provider it uses SYSTEM_ONE_DEFAULT_PROVIDER, typesafe when unset", async () => {
|
|
170
|
+
const { provider: _omit, ...noProvider } = request("typesafe");
|
|
171
|
+
const unset = recordAuthorization();
|
|
172
|
+
expect((await tool().handler(noProvider, extra())).isError).toBeFalsy();
|
|
173
|
+
expect(unset.map((s) => s.url)).toEqual(["https://api.typesafe.ai/v1/systemone"]);
|
|
174
|
+
|
|
175
|
+
const account = "0123456789abcdef0123456789abcdef";
|
|
176
|
+
await upsertSwarmConfig({
|
|
177
|
+
scope: "global",
|
|
178
|
+
key: "SYSTEM_ONE_DEFAULT_PROVIDER",
|
|
179
|
+
value: "cloudflare",
|
|
180
|
+
});
|
|
181
|
+
await upsertSwarmConfig({ scope: "global", key: "CLOUDFLARE_ACCOUNT_ID", value: account });
|
|
182
|
+
await upsertSwarmConfig({
|
|
183
|
+
scope: "global",
|
|
184
|
+
key: "CLOUDFLARE_API_TOKEN",
|
|
185
|
+
value: "cf-default-provider-token",
|
|
186
|
+
isSecret: true,
|
|
187
|
+
});
|
|
188
|
+
const sent: string[] = [];
|
|
189
|
+
globalThis.fetch = (async (input: RequestInfo | URL) => {
|
|
190
|
+
sent.push(input instanceof Request ? input.url : String(input));
|
|
191
|
+
return Response.json({
|
|
192
|
+
result: {
|
|
193
|
+
model: "clef",
|
|
194
|
+
answers: { act: { type: "noul", noul: 0.7 } },
|
|
195
|
+
usage: { input_tokens: 12, output_tokens: 0 },
|
|
196
|
+
},
|
|
197
|
+
success: true,
|
|
198
|
+
errors: [],
|
|
199
|
+
messages: [],
|
|
200
|
+
});
|
|
201
|
+
}) as typeof fetch;
|
|
202
|
+
const result = await tool().handler(noProvider, extra());
|
|
203
|
+
expect(result.isError).toBeFalsy();
|
|
204
|
+
expect(result.structuredContent).toMatchObject({
|
|
205
|
+
model: "clef",
|
|
206
|
+
answers: { act: { noul: 0.7 } },
|
|
207
|
+
});
|
|
208
|
+
expect(sent).toEqual([
|
|
209
|
+
`https://api.cloudflare.com/client/v4/accounts/${account}/ai/run/@cf/cloudflare/clef`,
|
|
210
|
+
]);
|
|
211
|
+
});
|
|
168
212
|
});
|