@manta-eu/horns-server 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manta-eu/horns-server",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "The Manta Horns worker as a library: Hono routes a Cloudflare host mounts on its own app.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,13 +26,29 @@
26
26
  "types": "./dist/shared.d.ts",
27
27
  "default": "./dist/shared.js"
28
28
  },
29
- "./llm": {
30
- "types": "./dist/llm.d.ts",
31
- "default": "./dist/llm.js"
32
- },
33
29
  "./typecheck": {
34
30
  "types": "./dist/typecheck.d.ts",
35
31
  "default": "./dist/typecheck.js"
32
+ },
33
+ "./studio-model": {
34
+ "types": "./dist/studio-model.d.ts",
35
+ "default": "./dist/studio-model.js"
36
+ },
37
+ "./studio-agent": {
38
+ "types": "./dist/studio-agent-routes.d.ts",
39
+ "default": "./dist/studio-agent-routes.js"
40
+ },
41
+ "./studio-agent-class": {
42
+ "types": "./dist/studio-agent-class.d.ts",
43
+ "default": "./dist/studio-agent-class.js"
44
+ },
45
+ "./session": {
46
+ "types": "./dist/session.d.ts",
47
+ "default": "./dist/session.js"
48
+ },
49
+ "./studio-worker": {
50
+ "types": "./dist/studio-worker.d.ts",
51
+ "default": "./dist/studio-worker.js"
36
52
  }
37
53
  },
38
54
  "publishConfig": {
@@ -40,20 +56,26 @@
40
56
  "access": "public"
41
57
  },
42
58
  "dependencies": {
59
+ "@ai-sdk/google-vertex": "^5.0.101",
60
+ "@ai-sdk/openai-compatible": "^3.0.62",
61
+ "@cloudflare/think": "0.19.0",
43
62
  "@manta-eu/client": "0.1.5",
44
63
  "@manta-eu/lib": "0.1.5",
45
64
  "@sentry/cloudflare": "^10.70.0",
46
65
  "@sentry/core": "^10.70.0",
66
+ "agents": "^0.24.0",
67
+ "ai": "^7.0.98",
47
68
  "hono": "^4.9.8",
48
69
  "ldkit": "2.9.0",
49
70
  "sparqljs": "^3.7.4",
50
71
  "typescript": "^5.9.0",
51
72
  "zod": "^4.5.4",
52
- "@manta-eu/horns": "0.1.3",
53
- "@manta-eu/horns-kit": "0.1.3"
73
+ "@manta-eu/horns": "0.2.0",
74
+ "@manta-eu/horns-kit": "0.2.0"
54
75
  },
55
76
  "devDependencies": {
56
77
  "@cloudflare/vite-plugin": "1.47.0",
78
+ "@cloudflare/workers-types": "^5.20261001.1",
57
79
  "@types/sparqljs": "^3.1.12",
58
80
  "rimraf": "^6.1.3",
59
81
  "typescript": "^5.9.0",
package/dist/llm.d.ts DELETED
@@ -1,45 +0,0 @@
1
- import { type StudioModel } from "@manta-eu/horns/studio";
2
- export type StudioModelEnv = {
3
- HORNS_LLM_BASE_URL?: string;
4
- HORNS_LLM_MODEL?: string;
5
- /**
6
- * `vllm` (the default) for the self-hosted Qwen, `openai` for anything else behind the gateway's
7
- * OpenAI-compatible route — Anthropic, OpenAI. It decides whether `chat_template_kwargs` is sent and
8
- * whether we carry a key of our own, because on any route but vLLM's the gateway holds the provider key.
9
- */
10
- HORNS_LLM_DIALECT?: string;
11
- HORNS_LLM_MAX_TOKENS?: string;
12
- VLLM_PRIMARY_AUTH_TOKEN?: string;
13
- CLOUDFLARE_AI_GATEWAY_TOKEN?: string;
14
- CLOUDFLARE_ENV?: string;
15
- /** The critic, on its own gateway route: a model that can look at a picture of what the Studio drew. */
16
- HORNS_CRITIC_BASE_URL?: string;
17
- HORNS_CRITIC_MODEL?: string;
18
- HORNS_CRITIC_DIALECT?: string;
19
- };
20
- export declare const aiGatewayHost = "gateway.ai.cloudflare.com";
21
- export declare const defaultMaxTokens = 6000;
22
- export type StudioModelResult = {
23
- ok: true;
24
- model: StudioModel;
25
- monitored: boolean;
26
- } | {
27
- ok: false;
28
- reason: string;
29
- };
30
- export declare function studioModelFromEnv(env: StudioModelEnv, context: {
31
- feature: string;
32
- datastoreId?: string;
33
- }, fetcher?: typeof fetch): StudioModelResult;
34
- /**
35
- * The critic's model, which is not the Studio's: it reads a picture of what the Studio drew, so it has to be a
36
- * model with image input. It goes through the same gateway for the same reason — every call is logged — but it
37
- * carries no key of ours. The gateway holds the provider key and injects it; sending one of ours would
38
- * override the stored key with something the provider never issued.
39
- *
40
- * Unconfigured is not an error: the caller falls back to the Studio's own model, which reads the card as text.
41
- */
42
- export declare function criticModelFromEnv(env: StudioModelEnv, context: {
43
- feature: string;
44
- datastoreId?: string;
45
- }, fetcher?: typeof fetch): StudioModelResult;
package/dist/llm.js DELETED
@@ -1,124 +0,0 @@
1
- import { isPresent } from "@manta-eu/lib/present";
2
- import { createOpenAICompatibleModel } from "@manta-eu/horns/studio";
3
- /** Anything but an explicit `openai` is vLLM, so a deployment that says nothing keeps the Qwen it had. */
4
- function dialectOf(configured) {
5
- return configured?.trim().toLowerCase() === "openai" ? "openai" : "vllm";
6
- }
7
- export const aiGatewayHost = "gateway.ai.cloudflare.com";
8
- // A round that runs past the gateway's ~100 s limit comes back as a timeout instead of an answer. Studio
9
- // rounds need around 3000 tokens, so this bounds a runaway thinking chain without cutting a real answer.
10
- export const defaultMaxTokens = 6000;
11
- const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
12
- function maxTokensOf(configured) {
13
- const parsed = Number(configured);
14
- return Number.isInteger(parsed) && parsed > 0 ? parsed : defaultMaxTokens;
15
- }
16
- // Every Qwen call goes through agents' AI Gateway route (`custom-vllm-manta`), so it is logged and metered
17
- // beside agents' own traffic and tagged as Horns'. The one exception is a loopback LLM stub in tests.
18
- export function studioModelFromEnv(env, context, fetcher) {
19
- const baseURL = env.HORNS_LLM_BASE_URL;
20
- const model = env.HORNS_LLM_MODEL;
21
- if (!isPresent(baseURL) || !isPresent(model)) {
22
- return { ok: false, reason: "HORNS_LLM_BASE_URL and HORNS_LLM_MODEL must both be set." };
23
- }
24
- if (!URL.canParse(baseURL))
25
- return { ok: false, reason: "HORNS_LLM_BASE_URL is not a URL." };
26
- const { hostname, protocol } = new URL(baseURL);
27
- const dialect = dialectOf(env.HORNS_LLM_DIALECT);
28
- const withFetch = fetcher ? { fetch: fetcher } : {};
29
- if (loopbackHosts.has(hostname)) {
30
- return {
31
- ok: true,
32
- monitored: false,
33
- model: createOpenAICompatibleModel({ baseURL, model, dialect, ...withFetch }),
34
- };
35
- }
36
- if (protocol !== "https:" || hostname !== aiGatewayHost) {
37
- return {
38
- ok: false,
39
- reason: `HORNS_LLM_BASE_URL must be the AI Gateway (${aiGatewayHost}) so model calls are logged, not ${hostname}.`,
40
- };
41
- }
42
- if (!isPresent(env.CLOUDFLARE_AI_GATEWAY_TOKEN)) {
43
- return { ok: false, reason: "CLOUDFLARE_AI_GATEWAY_TOKEN must be set to call the AI Gateway." };
44
- }
45
- // Only vLLM's route takes a key of ours. On any other the gateway holds the provider key, and sending one
46
- // would override the stored key with something the provider never issued.
47
- if (dialect === "vllm" && !isPresent(env.VLLM_PRIMARY_AUTH_TOKEN)) {
48
- return { ok: false, reason: "VLLM_PRIMARY_AUTH_TOKEN must be set for the gateway's vLLM route." };
49
- }
50
- return {
51
- ok: true,
52
- monitored: true,
53
- model: createOpenAICompatibleModel({
54
- baseURL,
55
- model,
56
- dialect,
57
- ...(dialect === "vllm" && isPresent(env.VLLM_PRIMARY_AUTH_TOKEN) ? { apiKey: env.VLLM_PRIMARY_AUTH_TOKEN } : {}),
58
- gatewayToken: env.CLOUDFLARE_AI_GATEWAY_TOKEN,
59
- maxTokens: maxTokensOf(env.HORNS_LLM_MAX_TOKENS),
60
- // No user identifiers: the gateway log is shared with agents.
61
- metadata: {
62
- app: "manta-horns",
63
- environment: env.CLOUDFLARE_ENV ?? "local",
64
- feature: context.feature,
65
- ...(isPresent(context.datastoreId) ? { datastore: context.datastoreId } : {}),
66
- },
67
- ...withFetch,
68
- }),
69
- };
70
- }
71
- /**
72
- * The critic's model, which is not the Studio's: it reads a picture of what the Studio drew, so it has to be a
73
- * model with image input. It goes through the same gateway for the same reason — every call is logged — but it
74
- * carries no key of ours. The gateway holds the provider key and injects it; sending one of ours would
75
- * override the stored key with something the provider never issued.
76
- *
77
- * Unconfigured is not an error: the caller falls back to the Studio's own model, which reads the card as text.
78
- */
79
- export function criticModelFromEnv(env, context, fetcher) {
80
- const baseURL = env.HORNS_CRITIC_BASE_URL;
81
- const model = env.HORNS_CRITIC_MODEL;
82
- if (!isPresent(baseURL) || !isPresent(model)) {
83
- return { ok: false, reason: "HORNS_CRITIC_BASE_URL and HORNS_CRITIC_MODEL must both be set." };
84
- }
85
- if (!URL.canParse(baseURL))
86
- return { ok: false, reason: "HORNS_CRITIC_BASE_URL is not a URL." };
87
- const { hostname, protocol } = new URL(baseURL);
88
- const dialect = dialectOf(env.HORNS_CRITIC_DIALECT);
89
- const withFetch = fetcher ? { fetch: fetcher } : {};
90
- if (loopbackHosts.has(hostname)) {
91
- return {
92
- ok: true,
93
- monitored: false,
94
- model: createOpenAICompatibleModel({ baseURL, model, dialect, ...withFetch }),
95
- };
96
- }
97
- if (protocol !== "https:" || hostname !== aiGatewayHost) {
98
- return {
99
- ok: false,
100
- reason: `HORNS_CRITIC_BASE_URL must be the AI Gateway (${aiGatewayHost}) so critic calls are logged, not ${hostname}.`,
101
- };
102
- }
103
- if (!isPresent(env.CLOUDFLARE_AI_GATEWAY_TOKEN)) {
104
- return { ok: false, reason: "CLOUDFLARE_AI_GATEWAY_TOKEN must be set to call the AI Gateway." };
105
- }
106
- return {
107
- ok: true,
108
- monitored: true,
109
- model: createOpenAICompatibleModel({
110
- baseURL,
111
- model,
112
- dialect,
113
- gatewayToken: env.CLOUDFLARE_AI_GATEWAY_TOKEN,
114
- maxTokens: maxTokensOf(env.HORNS_LLM_MAX_TOKENS),
115
- metadata: {
116
- app: "manta-horns",
117
- environment: env.CLOUDFLARE_ENV ?? "local",
118
- feature: context.feature,
119
- ...(isPresent(context.datastoreId) ? { datastore: context.datastoreId } : {}),
120
- },
121
- ...withFetch,
122
- }),
123
- };
124
- }