@camelai/run 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@ npm install @camelai/run
10
10
  ```
11
11
 
12
12
  Node 22 or later, Bun, Deno or Cloudflare Workers. Get an API key from the
13
- console at <https://agents.camelai.dev/console> and export it as
13
+ console at <https://run.camelai.com/console> and export it as
14
14
  `CAMELAI_API_KEY`.
15
15
 
16
16
  ```ts
@@ -55,10 +55,10 @@ await agents.close();
55
55
  `npm create @camelai/run-app`. `watchAgent` (`@camelai/run/watch`)
56
56
  shows an agent live with a browser token your server mints.
57
57
 
58
- Documentation: [Quickstart](https://agents.camelai.dev/docs/quickstart.md),
59
- [Concepts](https://agents.camelai.dev/docs/concepts.md),
60
- [SDK reference](https://agents.camelai.dev/docs/reference/sdk.md),
61
- and all of it as Markdown at <https://agents.camelai.dev/llms.txt>.
58
+ Documentation: [Quickstart](https://run.camelai.com/docs/quickstart.md),
59
+ [Concepts](https://run.camelai.com/docs/concepts.md),
60
+ [SDK reference](https://run.camelai.com/docs/reference/sdk.md),
61
+ and all of it as Markdown at <https://run.camelai.com/llms.txt>.
62
62
 
63
63
  `AgentRuntime` and `AgentClient`, the lower-level interface the SDK is built on,
64
64
  remain available. See the SDK reference's "Changes in 0.9" when upgrading.
@@ -15,7 +15,7 @@ declare const INSPECT: unique symbol;
15
15
  export interface AgentsOptions {
16
16
  /** Your API key (the console's). Default: the CAMELAI_API_KEY environment variable. */
17
17
  apiKey?: string;
18
- /** The runtime's origin. Default: CAMELAI_BASE_URL, else https://agents.camelai.dev. */
18
+ /** The runtime's origin. Default: CAMELAI_BASE_URL, else https://run.camelai.com. */
19
19
  url?: string;
20
20
  fetch?: typeof globalThis.fetch;
21
21
  /** Opens a local file to attach by its path; the Node entry sets it. */
@@ -36,7 +36,7 @@ export class Agents {
36
36
  */
37
37
  async upsert(key, config = {}) {
38
38
  if (!this.runtime.options.apiKey)
39
- throw new AgentError("Set apiKey (or the CAMELAI_API_KEY environment variable): create a key in the console at https://agents.camelai.dev");
39
+ throw new AgentError("Set apiKey (or the CAMELAI_API_KEY environment variable): create a key in the console at https://run.camelai.com");
40
40
  const options = createOptions(config);
41
41
  const { session } = await this.runtime.upsertAgent(key, options);
42
42
  // The upsert declared these tools already (between the agent's turns, if it runs).
@@ -46,7 +46,7 @@ export interface SendEvent<A extends AgentAuth = AgentAuth> {
46
46
  export interface AgentHandlerOptions<A extends AgentAuth = AgentAuth> {
47
47
  /** Your API key. Default: the CAMELAI_API_KEY environment variable. */
48
48
  apiKey?: string;
49
- /** The runtime's origin. Default: CAMELAI_BASE_URL, else https://agents.camelai.dev. */
49
+ /** The runtime's origin. Default: CAMELAI_BASE_URL, else https://run.camelai.com. */
50
50
  url?: string;
51
51
  /**
52
52
  * Your own session check, on every request. Return the user (and optionally which agent), or null to
@@ -4,7 +4,7 @@
4
4
  * whom it acts for and who is acting; these helpers verify it and hand your tools the identity.
5
5
  * Portable: fetch-style handlers and WebCrypto (Ed25519), so it runs on Workers, Node 22+, Bun and Deno.
6
6
  *
7
- * export default { fetch: serveTools(tools, { runtime: "https://agents.camelai.dev", tenant: "acme" }) };
7
+ * export default { fetch: serveTools(tools, { runtime: "https://run.camelai.com", tenant: "acme" }) };
8
8
  *
9
9
  * An identity means something only within your own tenant: other tenants' agents can be pointed at your
10
10
  * server too, so every check here requires `tenant`, and refuses tokens made for anyone else's agents.
@@ -17,9 +17,9 @@ export interface VerifyOptions {
17
17
  * refused. Required: another tenant can point its agents at your server and say they act for anyone.
18
18
  */
19
19
  tenant: string | string[];
20
- /** The runtime's URL (e.g. https://agents.camelai.dev): its keys are at /.well-known/jwks.json. */
20
+ /** The runtime's URL (e.g. https://run.camelai.com): its keys are at /.well-known/jwks.json. */
21
21
  runtime: string;
22
- /** The issuer tokens must name; the runtime's URL by default. */
22
+ /** The issuer tokens must name; the runtime's URL by default (camelRun's hosted runtime names itself https://agents.camelai.dev at either of its URLs). */
23
23
  issuer?: string;
24
24
  /** What tokens must be for: your server's URL as the runtime calls it (a definition's `url`, or its `audience`). */
25
25
  audience: string | string[];
@@ -4,7 +4,7 @@
4
4
  * whom it acts for and who is acting; these helpers verify it and hand your tools the identity.
5
5
  * Portable: fetch-style handlers and WebCrypto (Ed25519), so it runs on Workers, Node 22+, Bun and Deno.
6
6
  *
7
- * export default { fetch: serveTools(tools, { runtime: "https://agents.camelai.dev", tenant: "acme" }) };
7
+ * export default { fetch: serveTools(tools, { runtime: "https://run.camelai.com", tenant: "acme" }) };
8
8
  *
9
9
  * An identity means something only within your own tenant: other tenants' agents can be pointed at your
10
10
  * server too, so every check here requires `tenant`, and refuses tokens made for anyone else's agents.
@@ -15,6 +15,15 @@ export class RuntimeTokenError extends Error {
15
15
  constructor(message) { super(message); this.name = "RuntimeTokenError"; }
16
16
  }
17
17
  const trim = (url) => url.replace(/\/+$/, "");
18
+ /** camelRun's hosted runtime answers at both names, and signs as the first it had, which tool servers already check. */
19
+ const HOSTED = ["https://run.camelai.com", "https://agents.camelai.dev"];
20
+ const HOSTED_ISSUER = "https://agents.camelai.dev";
21
+ const issuerOf = (options) => {
22
+ if (options.issuer)
23
+ return trim(options.issuer);
24
+ const runtime = trim(options.runtime);
25
+ return HOSTED.includes(runtime) ? HOSTED_ISSUER : runtime;
26
+ };
18
27
  const decoder = new TextDecoder();
19
28
  function base64url(text) {
20
29
  const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text.length / 4) * 4, "="));
@@ -86,7 +95,7 @@ export async function verifyRuntimeToken(token, options) {
86
95
  throw new RuntimeTokenError("Token signature does not verify");
87
96
  const claims = part(pieces[1]);
88
97
  const now = Math.floor(Date.now() / 1000), skew = options.clockTolerance ?? 30;
89
- if (claims.iss !== trim(options.issuer ?? runtime))
98
+ if (claims.iss !== issuerOf(options))
90
99
  throw new RuntimeTokenError("Token is from another issuer");
91
100
  if (![options.tenant].flat().includes(claims.tenant))
92
101
  throw new RuntimeTokenError("Token is for another tenant's agent");
@@ -135,7 +144,7 @@ export function runtimeIdentity(extra) {
135
144
  export function serveTools(tools, options) {
136
145
  requireTenant(options);
137
146
  const server = typeof tools.listTools === "function" && typeof tools.callTool === "function" ? tools : toolServer(tools);
138
- const issuer = trim(options.issuer ?? options.runtime);
147
+ const issuer = issuerOf(options);
139
148
  const json = (status, body, headers = {}) => new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json", ...headers } });
140
149
  const WELL_KNOWN = "/.well-known/oauth-protected-resource";
141
150
  return async (request) => {
@@ -160,9 +160,9 @@ export declare function answerMcp(message: Record<string, any>, server: ToolServ
160
160
  /** `tool({...})` definitions as an attached MCP server: JSON results become a text block (and structured content for objects). */
161
161
  export declare function toolServer(tools: Tools): ToolServer;
162
162
  /** The hosted runtime; `url` points elsewhere (a self-hosted runtime, or http://127.0.0.1:8790 in development). */
163
- export declare const DEFAULT_URL = "https://agents.camelai.dev";
163
+ export declare const DEFAULT_URL = "https://run.camelai.com";
164
164
  export interface RuntimeOptions {
165
- /** The runtime's origin. Default https://agents.camelai.dev. */
165
+ /** The runtime's origin. Default https://run.camelai.com. */
166
166
  url?: string;
167
167
  apiKey?: string;
168
168
  /** Injectable for tests, observability, or an application's HTTP stack. */
@@ -183,7 +183,8 @@ export interface AgentOptions {
183
183
  * throws goes to `onError`. A `message_update` is its delta alone (`assistantMessageEvent`), without the
184
184
  * message it updates: fold that from its `message_start` and the deltas since. Where the stream
185
185
  * cannot replay (a first connect, or a reconnect after the host's buffer moved on), the first event
186
- * is a `{ type: "snapshot", turn }` of the running turn to fold from.
186
+ * is a `{ type: "snapshot", turn }` of the running turn to fold from. `close()` stops it: events still
187
+ * queued are dropped.
187
188
  */
188
189
  onEvent?: (event: AgentEvent, requestId?: string) => unknown | Promise<unknown>;
189
190
  /**
@@ -359,6 +360,8 @@ interface SourceOptions {
359
360
  }
360
361
  export interface DefinitionInput {
361
362
  name: string;
363
+ /** What its agents are for: shown to models as the description of each agent's MCP tool (/v1/agents/:id/mcp). */
364
+ description?: string;
362
365
  model?: string;
363
366
  systemPrompt?: string;
364
367
  thinkingLevel?: ThinkingLevel;
@@ -147,7 +147,7 @@ export function toolServer(tools) {
147
147
  };
148
148
  }
149
149
  /** The hosted runtime; `url` points elsewhere (a self-hosted runtime, or http://127.0.0.1:8790 in development). */
150
- export const DEFAULT_URL = "https://agents.camelai.dev";
150
+ export const DEFAULT_URL = "https://run.camelai.com";
151
151
  export class AgentError extends Error {
152
152
  /** The HTTP status, or 0 for a failure that is not an HTTP response's (a run's, the connection's). */
153
153
  status;
@@ -703,7 +703,7 @@ export class AgentClient {
703
703
  }
704
704
  }
705
705
  const onEvent = this.options.onEvent;
706
- if (!onEvent)
706
+ if (!onEvent || this.closed)
707
707
  return;
708
708
  if (this.queued >= MAX_QUEUED_EVENTS && event.type === "message_update") {
709
709
  if (this.dropped++ === 0)
@@ -712,8 +712,10 @@ export class AgentClient {
712
712
  }
713
713
  this.queued++;
714
714
  this.dispatching = this.dispatching.then(async () => {
715
+ // A closed client calls onEvent no more: events still queued are dropped.
715
716
  try {
716
- await onEvent(event, requestId);
717
+ if (!this.closed)
718
+ await onEvent(event, requestId);
717
719
  }
718
720
  catch (error) {
719
721
  this.report(error);
@@ -1019,7 +1021,7 @@ export class AgentClient {
1019
1021
  waiter.reject(new AgentError("Client closed; request may still be running", 0, id));
1020
1022
  this.pending.clear();
1021
1023
  await this.loop;
1022
- // Events received before closing still reach onEvent, but a handler that never returns cannot hang shutdown.
1024
+ // onEvent is called no more; the call in progress may finish, but one that never returns cannot hang shutdown.
1023
1025
  let timer;
1024
1026
  await Promise.race([this.dispatching, new Promise(resolve => { timer = setTimeout(resolve, 2000); })]);
1025
1027
  clearTimeout(timer);
@@ -263,12 +263,13 @@ export function watchAgent(options) {
263
263
  }
264
264
  else
265
265
  next = undefined;
266
- // No turn in it: none runs, or the token does not show it. Its state says which, where the token reads it.
266
+ await newest();
267
+ // No turn in it: none runs, or the token does not show it. Its state says which, where the token reads it: read
268
+ // after history, so a run that began in between is running here too, never a message in history with no turn.
267
269
  if (!turn) {
268
270
  const known = await json200("/state").catch(() => undefined);
269
271
  state.running = !!known?.requests?.some(request => request.state === "running" && request.began && ["prompt", "continue", "resume"].includes(request.method));
270
272
  }
271
- await newest();
272
273
  return;
273
274
  }
274
275
  if (data.type === "response") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camelai/run",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "SDK for camelRun: define tools in your app, and the runtime runs the model loop, history and sandbox.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -66,7 +66,7 @@
66
66
  "dependencies": {
67
67
  "typebox": "1.1.38"
68
68
  },
69
- "homepage": "https://agents.camelai.dev",
69
+ "homepage": "https://run.camelai.com",
70
70
  "peerDependencies": {
71
71
  "@modelcontextprotocol/sdk": "^1.30.1"
72
72
  },