@cerebro-labs/agentnava-sdk 0.46.1 → 0.50.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/README.md +11 -0
- package/agent.d.ts +49 -33
- package/agent.js +41 -43
- package/agent.js.map +1 -1
- package/conversation.d.ts +3 -1
- package/conversation.js +6 -1
- package/conversation.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.js.map +1 -1
- package/load-agent-dir.js +2 -21
- package/load-agent-dir.js.map +1 -1
- package/package.json +1 -1
- package/project.d.ts +1 -1
- package/project.js +11 -1
- package/project.js.map +1 -1
- package/turn.d.ts +6 -2
- package/turn.js +85 -19
- package/turn.js.map +1 -1
- package/types.d.ts +79 -21
- package/types.js +5 -1
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
package/conversation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"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","sourcesContent":["// A conversation: one continuous exchange with an agent, with its own history\n// and its own filesystem. Two conversations share nothing.\n\nimport { isBindings, refuseConnections, type SessionBindings, secretIdsOf, secretsIn } from './attach.ts';\nimport { AgentNavaError } from './errors.ts';\nimport { type Http, turnRecoveryMessage } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport type {\n\tAccountNeeded,\n\tAgentConversationData,\n\tAttachment,\n\tBindings,\n\tFileEntry,\n\tMessage,\n\tRequirement,\n} from './types.ts';\n\nexport interface AskOptions {\n\tattachments?: Attachment[];\n\t/**\n\t * Stop receiving this turn's response. An already-aborted signal prevents\n\t * submission, so no turn is accepted or charged for this call. After the\n\t * server accepts a turn, aborting stops observation: accepted work can\n\t * continue and normal billing applies, with no automatic refund or server\n\t * cancellation. Read conversation.messages() before asking again.\n\t */\n\tsignal?: AbortSignal;\n\t/** Stable identity for one submission. Reuse it only for the same content\n\t * after a lost response; a new intentional turn needs a new key. Defaults to\n\t * a generated key. Use 1–128 letters, digits, dots, underscores, colons or hyphens. */\n\tidempotencyKey?: string;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\t/** Seconds the link stays valid. */\n\tttl?: number;\n}\n\n/**\n * A conversation you can work with.\n *\n * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT, reachable as `agentId` once\n * this handle has read anything. A conversation is an agent doing a piece of\n * work; there is no such thing as a conversation with nobody.\n *\n * ADDRESSED FROM ITS PARENT: `agent.conversation(id)`, or\n * `project.conversation(id)` for one a project owns. It used to be\n * `ws.conversation(id)`, which made the parent look optional and is how this\n * SDK came to describe conversations as parentless in its own reference. The\n * objection to moving it was that a caller would then store two ids; in\n * practice the agent id is a constant in their own code, not a per-row value.\n *\n * Holding one makes NO request. The first call on it is the first round trip,\n * because fetching a conversation just to send it a message would be a round\n * trip for nothing.\n */\nexport class Conversation {\n\treadonly id: string;\n\treadonly #http: Http;\n\t/** Whatever the call that produced this object already knew. Present when it\n\t * came from `agent.start()`, absent when it came from `agent.conversation(id)`,\n\t * which is exactly the difference between having made a request and not. */\n\tprivate snapshot?: AgentConversationData;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentConversationData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/conversations/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * The agent whose conversation this is.\n\t *\n\t * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT. That is the model, and it\n\t * is worth stating on the object because the SDK used to hold this value and\n\t * never show it: a method read it internally and nothing else could, which\n\t * made a conversation look parentless from outside.\n\t *\n\t * Undefined on a conversation you addressed by id and have not read yet,\n\t * for the same reason `ready` is: the handle has made no request. Call\n\t * `get()` to fill it in.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * conversation.agentId === agent.id; // already known, no round trip\n\t * ```\n\t */\n\tget agentId(): string | undefined {\n\t\treturn this.snapshot?.agentId;\n\t}\n\n\t/** True when nothing REQUIRED is outstanding.\n\t *\n\t * Reads from the last snapshot rather than the network, so it is a property\n\t * and not a call. It is only meaningful on a conversation you just started\n\t * or just refreshed; call `get()` to bring it up to date. */\n\tget ready(): boolean | undefined {\n\t\treturn this.snapshot?.ready;\n\t}\n\n\t/** What the conversation is still missing. Same caveat as `ready`. */\n\tget requires(): Requirement[] {\n\t\treturn this.snapshot?.requires ?? [];\n\t}\n\n\t/** The number of the agent version it runs, from the last snapshot. Same\n\t * caveat as `ready`: a schedule naming this conversation moves it to the\n\t * latest version, and only `get()` sees that. */\n\tget version(): number | undefined {\n\t\treturn this.snapshot?.version;\n\t}\n\n\tasync get(): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.get<AgentConversationData>(this.base);\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Ask, and get the reply.\n\t *\n\t * The message is sent HERE, not when you await or iterate the result, so\n\t * calling this twice with new keys asks twice. Await it for the answer or iterate it for\n\t * the events; doing both to the same turn throws rather than replaying.\n\t */\n\task(text: string, options: AskOptions = {}): Turn {\n\t\t// One key for this submission, retained in the transport request if a\n\t\t// fetch implementation retries it. A caller may retain their own key for\n\t\t// an explicit retry after losing the first response.\n\t\tconst idempotencyKey = options.idempotencyKey ?? newTurnKey();\n\t\tif (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {\n\t\t\tthrow new AgentNavaError({ message: 'idempotencyKey must contain 1–128 letters, digits, dots, underscores, colons or hyphens.', code: 'invalid_idempotency_key', status: 400 });\n\t\t}\n\t\tconst response = this.#http.send(`${this.base}/messages`, {\n\t\t\tmethod: 'POST',\n\t\t\tidempotencyKey,\n\t\t\tbody: {\n\t\t\t\tcontent: text,\n\t\t\t\t...(options.attachments?.length && { attachments: options.attachments }),\n\t\t\t},\n\t\t\t...(options.signal && { signal: options.signal }),\n\t\t});\n\t\treturn makeTurn(response, turnRecoveryMessage(`${this.base}/messages`, idempotencyKey));\n\t}\n\n\tasync messages(): Promise<Message[]> {\n\t\tconst res = await this.#http.get<{ messages: Message[] }>(`${this.base}/messages`);\n\t\treturn res.messages ?? [];\n\t}\n\n\t/** End it, and its drive with it. */\n\tasync end(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/** Supply the secrets this conversation's agent declared at 'conversation'\n\t * scope. Merges: sending one secret does not clear another.\n\t *\n\t * WRAPPED IN `sessionBindings`, and that envelope is load-bearing. This\n\t * posted the object at the top level, while `addSessionBindings` reads\n\t * `body.sessionBindings`. The handler therefore saw undefined, and undefined\n\t * is NOT an error there: a launch carrying no bindings is legitimate, so it\n\t * normalises to `{}`.\n\t *\n\t * So this returned 200 and bound nothing, and the failure appeared much\n\t * later as an agent that could not use a credential somebody had plainly\n\t * supplied.\n\t *\n\t * `agent.bind` had the same class of bug against its own route and its\n\t * comment records it. This is the second one, found by reading both\n\t * together. */\n\tasync #applyBindings(bindings: SessionBindings): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.post<AgentConversationData>(`${this.base}/bindings`, {\n\t\t\tsessionBindings: bindings,\n\t\t});\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Give THIS conversation a secret, and nothing else.\n\t *\n\t * ```ts\n\t * await conversation.attach(key); // a stored Secret, by id\n\t * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE\n\t * ```\n\t *\n\t * The narrowest scope there is: it reaches this conversation only.\n\t *\n\t * SECRETS ONLY, since 0.44. An account is never attached to a conversation\n\t * any more: the agent's setup names whose account each connection uses, and\n\t * for the end user's account the turn uses the account of the person this\n\t * conversation is for. One handed in here is refused by name\n\t * (`not_a_secret`) rather than dropped. `accountsNeeded()` says which of\n\t * that person's accounts are still missing.\n\t *\n\t * PREFER `agent.start({ attach })` when you already know at launch. Between\n\t * a start and a later attach there is a window in which the conversation\n\t * exists, can be asked something, and would answer without the secret.\n\t */\n\tasync attach(what: Secret | Secret[] | Bindings): Promise<AgentConversationData> {\n\t\tif (isBindings(what)) {\n\t\t\trefuseConnections(what);\n\t\t\treturn this.#applyBindings(what);\n\t\t}\n\t\tconst secretIds = await secretIdsOf(secretsIn(what));\n\t\treturn this.#applyBindings({ ...(secretIds && { secretIds }) });\n\t}\n\n\t/**\n\t * The end user's accounts this conversation still needs, each with the link\n\t * that person signs in at.\n\t *\n\t * One for every connection to the end user's account (`endUser: true`)\n\t * whose app the person this conversation is for has not connected yet.\n\t * Empty when nothing is missing. They sign in ONCE: the account is remembered for\n\t * them, so their next conversation does not ask again.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: user.id });\n\t * for (const { app, url } of await conversation.accountsNeeded()) {\n\t * showToUser(app, url); // popup, redirect, email, your choice\n\t * }\n\t * ```\n\t *\n\t * Ask again once they are back: an empty list means nothing is missing.\n\t *\n\t * A conversation is never refused for a missing account. It starts, and\n\t * the agent says what it could not reach; this is how your application\n\t * finds out first.\n\t */\n\tasync accountsNeeded(): Promise<AccountNeeded[]> {\n\t\tconst res = await this.#http.get<{ needed: AccountNeeded[] }>(`${this.base}/accounts-needed`);\n\t\treturn res.needed ?? [];\n\t}\n\n\t/**\n\t * Wait until this conversation has everything it needs: until `ready`.\n\t *\n\t * So you do not have to invent a polling loop or work out which field to\n\t * believe.\n\t *\n\t * ```ts\n\t * await conversation.waitUntilReady();\n\t * ```\n\t *\n\t * Returns as soon as nothing REQUIRED is outstanding, and returns the\n\t * conversation so you do not need a second call. An optional requirement\n\t * left unmet does not hold this up, for the same reason it does not make\n\t * `ready` false: the conversation genuinely works without it.\n\t *\n\t * ON TIMEOUT it throws naming what is still missing, because \"timed out\" on\n\t * its own sends you looking in the wrong place. The code is\n\t * `conversation_not_ready` and it is safe to call again: a person who has\n\t * not finished signing in yet may simply need longer.\n\t *\n\t * This polls rather than subscribing. For a step with a human in the middle,\n\t * a few seconds of latency costs nothing, and it keeps the method usable\n\t * from any runtime without a socket.\n\t */\n\tasync waitUntilReady(options: {\n\t\t/** Give up after this long. Default 2 minutes, which is generous for a\n\t\t * sign-in and short enough that a stuck flow does not hang a request. */\n\t\ttimeoutMs?: number;\n\t\t/** Gap between checks. Default 2 seconds. */\n\t\tintervalMs?: number;\n\t\t/** Cancel the wait, e.g. when the caller's own request is abandoned. */\n\t\tsignal?: AbortSignal;\n\t} = {}): Promise<AgentConversationData> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// Checked BEFORE the first sleep. A conversation that is already ready\n\t\t// should cost one request, not one request plus an interval.\n\t\tlet data = await this.get();\n\t\twhile (!data.ready) {\n\t\t\tif (options.signal?.aborted) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: 'waitUntilReady was cancelled',\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t// Name the outstanding REQUIRED items. The optional ones are not why\n\t\t\t\t// this failed and listing them would send someone after the wrong\n\t\t\t\t// thing.\n\t\t\t\tconst outstanding = data.requires\n\t\t\t\t\t.filter((r) => r.required)\n\t\t\t\t\t.map((r) => `${r.kind} ${r.name}`);\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: outstanding.length\n\t\t\t\t\t\t? `Conversation ${this.id} is still waiting on: ${outstanding.join(', ')}`\n\t\t\t\t\t\t: `Conversation ${this.id} did not become ready within ${timeoutMs}ms`,\n\t\t\t\t\tcode: 'conversation_not_ready',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Never sleep past the deadline, so the timeout means what it says.\n\t\t\tawait sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())), options.signal);\n\t\t\tdata = await this.get();\n\t\t}\n\t\treturn data;\n\t}\n\n\t// ---------------------------------------------------------------- the drive\n\n\t/** List one directory. `path`, not `dir`: it is the same parameter every other\n\t * file route on this API takes, and one name for one idea beats a prettier\n\t * name used in one place. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: DriveEntry[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []).map(toFileEntry);\n\t}\n\n\t/** Read a text file. */\n\tasync readFile(path: string): Promise<string> {\n\t\t// The route answers JSON `{path, content}`, not bare text. The envelope is\n\t\t// worth keeping on the wire (the canonical path comes back with it), so the\n\t\t// unwrapping happens here: a caller asked for the file, not for a wrapper.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync readFileBytes(path: string): Promise<Uint8Array> {\n\t\tconst res = await this.#http.send(`${this.base}/files/raw`, { query: { path } });\n\t\treturn new Uint8Array(await res.arrayBuffer());\n\t}\n\n\t/**\n\t * Put a file into the conversation's drive.\n\t *\n\t * This is how an application STAGES material: write the contract in, then ask\n\t * the question about it. Anywhere under the root is fine; `input/` is a\n\t * convention in the docs and nothing enforces it, because the agent and your\n\t * application share one namespace.\n\t */\n\tasync writeFile(\n\t\tpath: string,\n\t\tbytes: Uint8Array | ArrayBuffer,\n\t\tcontentType?: string,\n\t): Promise<FileEntry> {\n\t\treturn this.#http.request<FileEntry>(`${this.base}/files`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbytes,\n\t\t\tquery: { path },\n\t\t\t...(contentType && { contentType }),\n\t\t});\n\t}\n\n\tasync removeFile(path: string): Promise<void> {\n\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t}\n\n\t/**\n\t * A link to one file that carries its own authorisation.\n\t *\n\t * A browser cannot hold an API key, so a Download button has no way to prove\n\t * it is allowed. This grants that one file for `ttl` seconds and nothing\n\t * else.\n\t */\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\nlet fallbackTurnSequence = 0;\nfunction newTurnKey(): string {\n\treturn globalThis.crypto?.randomUUID?.() ??\n\t\t`turn-${Date.now().toString(36)}-${++fallbackTurnSequence}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** One entry as the Drive returns it. */\ninterface DriveEntry {\n\tname: string;\n\tpath: string;\n\tisDir: boolean;\n\tsize: number;\n\t/** Epoch milliseconds. */\n\tmtime: number;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n\ttxt: 'text/plain',\n\tmd: 'text/markdown',\n\tjson: 'application/json',\n\tcsv: 'text/csv',\n\thtml: 'text/html',\n\tjs: 'text/javascript',\n\tts: 'text/typescript',\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tgif: 'image/gif',\n\twebp: 'image/webp',\n\tsvg: 'image/svg+xml',\n\tpdf: 'application/pdf',\n};\n\n/**\n * A Drive entry as the SDK's FileEntry.\n *\n * The Drive speaks `isDir` and `mtime`; the SDK speaks `kind` and an ISO\n * `modifiedAt`. Translating at the edge is the SDK's job: the Drive's shape is\n * what apps/web's DrivePane reads, and changing it to suit this would break a\n * working screen to save a mapping function.\n *\n * `mimeType` is inferred from the extension, because the Drive does not store\n * one (see apps/backend-workers/src/sandbox/drive-reader.ts, which infers it the\n * same way on read).\n */\nfunction toFileEntry(entry: DriveEntry): FileEntry {\n\tconst ext = entry.path.slice(entry.path.lastIndexOf('.') + 1).toLowerCase();\n\treturn {\n\t\tpath: entry.path,\n\t\tkind: entry.isDir ? 'directory' : 'file',\n\t\tmimeType: entry.isDir ? 'inode/directory' : (CONTENT_TYPES[ext] ?? 'application/octet-stream'),\n\t\tsize: entry.size,\n\t\tmodifiedAt: new Date(entry.mtime).toISOString(),\n\t};\n}\n\n/** Wait, and stop waiting early if the caller aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(done, ms);\n\t\tfunction done() {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener('abort', done);\n\t\t\tresolve();\n\t\t}\n\t\tsignal?.addEventListener('abort', done, { once: true });\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conversation.js","sourceRoot":"","sources":["../src/conversation.ts"],"names":[],"mappings":"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","sourcesContent":["// A conversation: one continuous exchange with an agent, with its own history\n// and its own filesystem. Two conversations share nothing.\n\nimport { isBindings, refuseConnections, type SessionBindings, secretIdsOf, secretsIn } from './attach.ts';\nimport { AgentNavaError } from './errors.ts';\nimport { type Http, turnRecoveryMessage } from './http.ts';\nimport type { Secret } from './secret.ts';\nimport { makeTurn, type Turn } from './turn.ts';\nimport type {\n\tAccountNeeded,\n\tAgentConversationData,\n\tAttachment,\n\tBindings,\n\tFileEntry,\n\tMessage,\n\tRequirement,\n} from './types.ts';\n\nexport interface AskOptions {\n\tattachments?: Attachment[];\n\t/**\n\t * Stop receiving this turn's response. An already-aborted signal prevents\n\t * submission, so no turn is accepted or charged for this call. After the\n\t * server accepts a turn, aborting stops observation: accepted work can\n\t * continue and normal billing applies, with no automatic refund or server\n\t * cancellation. Read conversation.messages() before asking again.\n\t */\n\tsignal?: AbortSignal;\n\t/** Stable identity for one submission. Reuse it only for the same content\n\t * after a lost response; a new intentional turn needs a new key. Defaults to\n\t * a generated key. Use 1–128 letters, digits, dots, underscores, colons or hyphens. */\n\tidempotencyKey?: string;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\t/** Seconds the link stays valid. */\n\tttl?: number;\n}\n\n/**\n * A conversation you can work with.\n *\n * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT, reachable as `agentId` once\n * this handle has read anything. A conversation is an agent doing a piece of\n * work; there is no such thing as a conversation with nobody.\n *\n * ADDRESSED FROM ITS PARENT: `agent.conversation(id)`, or\n * `project.conversation(id)` for one a project owns. It used to be\n * `ws.conversation(id)`, which made the parent look optional and is how this\n * SDK came to describe conversations as parentless in its own reference. The\n * objection to moving it was that a caller would then store two ids; in\n * practice the agent id is a constant in their own code, not a per-row value.\n *\n * Holding one makes NO request. The first call on it is the first round trip,\n * because fetching a conversation just to send it a message would be a round\n * trip for nothing.\n */\nexport class Conversation {\n\treadonly id: string;\n\treadonly #http: Http;\n\t/** Whatever the call that produced this object already knew. Present when it\n\t * came from `agent.start()`, absent when it came from `agent.conversation(id)`,\n\t * which is exactly the difference between having made a request and not. */\n\tprivate snapshot?: AgentConversationData;\n\t/** This handle's screen can draw widgets (`start` or `conversation(id)` with\n\t * `widgets: true`). Sent with every turn, since the screen is what decides. */\n\treadonly #widgets: boolean;\n\n\tconstructor(http: Http, id: string, snapshot?: AgentConversationData, options: { widgets?: boolean } = {}) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t\tthis.#widgets = options.widgets === true;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/conversations/${encodeURIComponent(this.id)}`;\n\t}\n\n\t/**\n\t * The agent whose conversation this is.\n\t *\n\t * EVERY CONVERSATION BELONGS TO EXACTLY ONE AGENT. That is the model, and it\n\t * is worth stating on the object because the SDK used to hold this value and\n\t * never show it: a method read it internally and nothing else could, which\n\t * made a conversation look parentless from outside.\n\t *\n\t * Undefined on a conversation you addressed by id and have not read yet,\n\t * for the same reason `ready` is: the handle has made no request. Call\n\t * `get()` to fill it in.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: row.userId });\n\t * conversation.agentId === agent.id; // already known, no round trip\n\t * ```\n\t */\n\tget agentId(): string | undefined {\n\t\treturn this.snapshot?.agentId;\n\t}\n\n\t/** True when nothing REQUIRED is outstanding.\n\t *\n\t * Reads from the last snapshot rather than the network, so it is a property\n\t * and not a call. It is only meaningful on a conversation you just started\n\t * or just refreshed; call `get()` to bring it up to date. */\n\tget ready(): boolean | undefined {\n\t\treturn this.snapshot?.ready;\n\t}\n\n\t/** What the conversation is still missing. Same caveat as `ready`. */\n\tget requires(): Requirement[] {\n\t\treturn this.snapshot?.requires ?? [];\n\t}\n\n\t/** The number of the agent version it runs, from the last snapshot. Same\n\t * caveat as `ready`: a schedule naming this conversation moves it to the\n\t * latest version, and only `get()` sees that. */\n\tget version(): number | undefined {\n\t\treturn this.snapshot?.version;\n\t}\n\n\tasync get(): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.get<AgentConversationData>(this.base);\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Ask, and get the reply.\n\t *\n\t * The message is sent HERE, not when you await or iterate the result, so\n\t * calling this twice with new keys asks twice. Await it for the answer or iterate it for\n\t * the events; doing both to the same turn throws rather than replaying.\n\t */\n\task(text: string, options: AskOptions = {}): Turn {\n\t\t// One key for this submission, retained in the transport request if a\n\t\t// fetch implementation retries it. A caller may retain their own key for\n\t\t// an explicit retry after losing the first response.\n\t\tconst idempotencyKey = options.idempotencyKey ?? newTurnKey();\n\t\tif (!/^[A-Za-z0-9._:-]{1,128}$/.test(idempotencyKey)) {\n\t\t\tthrow new AgentNavaError({ message: 'idempotencyKey must contain 1–128 letters, digits, dots, underscores, colons or hyphens.', code: 'invalid_idempotency_key', status: 400 });\n\t\t}\n\t\tconst response = this.#http.send(`${this.base}/messages`, {\n\t\t\tmethod: 'POST',\n\t\t\tidempotencyKey,\n\t\t\tbody: {\n\t\t\t\tcontent: text,\n\t\t\t\t...(options.attachments?.length && { attachments: options.attachments }),\n\t\t\t\t...(this.#widgets && { widgets: true }),\n\t\t\t},\n\t\t\t...(options.signal && { signal: options.signal }),\n\t\t});\n\t\treturn makeTurn(response, turnRecoveryMessage(`${this.base}/messages`, idempotencyKey));\n\t}\n\n\tasync messages(): Promise<Message[]> {\n\t\tconst res = await this.#http.get<{ messages: Message[] }>(`${this.base}/messages`);\n\t\treturn res.messages ?? [];\n\t}\n\n\t/** End it, and its drive with it. */\n\tasync end(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/** Supply the secrets this conversation's agent declared at 'conversation'\n\t * scope. Merges: sending one secret does not clear another.\n\t *\n\t * WRAPPED IN `sessionBindings`, and that envelope is load-bearing. This\n\t * posted the object at the top level, while `addSessionBindings` reads\n\t * `body.sessionBindings`. The handler therefore saw undefined, and undefined\n\t * is NOT an error there: a launch carrying no bindings is legitimate, so it\n\t * normalises to `{}`.\n\t *\n\t * So this returned 200 and bound nothing, and the failure appeared much\n\t * later as an agent that could not use a credential somebody had plainly\n\t * supplied.\n\t *\n\t * `agent.bind` had the same class of bug against its own route and its\n\t * comment records it. This is the second one, found by reading both\n\t * together. */\n\tasync #applyBindings(bindings: SessionBindings): Promise<AgentConversationData> {\n\t\tconst data = await this.#http.post<AgentConversationData>(`${this.base}/bindings`, {\n\t\t\tsessionBindings: bindings,\n\t\t});\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\t/**\n\t * Give THIS conversation a secret, and nothing else.\n\t *\n\t * ```ts\n\t * await conversation.attach(key); // a stored Secret, by id\n\t * await conversation.attach({ secrets: { KEY: value } }); // a secret VALUE\n\t * ```\n\t *\n\t * The narrowest scope there is: it reaches this conversation only.\n\t *\n\t * SECRETS ONLY, since 0.44. An account is never attached to a conversation\n\t * any more: the agent's setup names whose account each connection uses, and\n\t * for the end user's account the turn uses the account of the person this\n\t * conversation is for. One handed in here is refused by name\n\t * (`not_a_secret`) rather than dropped. `accountsNeeded()` says which of\n\t * that person's accounts are still missing.\n\t *\n\t * PREFER `agent.start({ attach })` when you already know at launch. Between\n\t * a start and a later attach there is a window in which the conversation\n\t * exists, can be asked something, and would answer without the secret.\n\t */\n\tasync attach(what: Secret | Secret[] | Bindings): Promise<AgentConversationData> {\n\t\tif (isBindings(what)) {\n\t\t\trefuseConnections(what);\n\t\t\treturn this.#applyBindings(what);\n\t\t}\n\t\tconst secretIds = await secretIdsOf(secretsIn(what));\n\t\treturn this.#applyBindings({ ...(secretIds && { secretIds }) });\n\t}\n\n\t/**\n\t * The end user's accounts this conversation still needs, each with the link\n\t * that person signs in at.\n\t *\n\t * One for every connection to the end user's account (`endUser: true`)\n\t * whose app the person this conversation is for has not connected yet.\n\t * Empty when nothing is missing. They sign in ONCE: the account is remembered for\n\t * them, so their next conversation does not ask again.\n\t *\n\t * ```ts\n\t * const conversation = await agent.start({ endUserId: user.id });\n\t * for (const { app, url } of await conversation.accountsNeeded()) {\n\t * showToUser(app, url); // popup, redirect, email, your choice\n\t * }\n\t * ```\n\t *\n\t * Ask again once they are back: an empty list means nothing is missing.\n\t *\n\t * A conversation is never refused for a missing account. It starts, and\n\t * the agent says what it could not reach; this is how your application\n\t * finds out first.\n\t */\n\tasync accountsNeeded(): Promise<AccountNeeded[]> {\n\t\tconst res = await this.#http.get<{ needed: AccountNeeded[] }>(`${this.base}/accounts-needed`);\n\t\treturn res.needed ?? [];\n\t}\n\n\t/**\n\t * Wait until this conversation has everything it needs: until `ready`.\n\t *\n\t * So you do not have to invent a polling loop or work out which field to\n\t * believe.\n\t *\n\t * ```ts\n\t * await conversation.waitUntilReady();\n\t * ```\n\t *\n\t * Returns as soon as nothing REQUIRED is outstanding, and returns the\n\t * conversation so you do not need a second call. An optional requirement\n\t * left unmet does not hold this up, for the same reason it does not make\n\t * `ready` false: the conversation genuinely works without it.\n\t *\n\t * ON TIMEOUT it throws naming what is still missing, because \"timed out\" on\n\t * its own sends you looking in the wrong place. The code is\n\t * `conversation_not_ready` and it is safe to call again: a person who has\n\t * not finished signing in yet may simply need longer.\n\t *\n\t * This polls rather than subscribing. For a step with a human in the middle,\n\t * a few seconds of latency costs nothing, and it keeps the method usable\n\t * from any runtime without a socket.\n\t */\n\tasync waitUntilReady(options: {\n\t\t/** Give up after this long. Default 2 minutes, which is generous for a\n\t\t * sign-in and short enough that a stuck flow does not hang a request. */\n\t\ttimeoutMs?: number;\n\t\t/** Gap between checks. Default 2 seconds. */\n\t\tintervalMs?: number;\n\t\t/** Cancel the wait, e.g. when the caller's own request is abandoned. */\n\t\tsignal?: AbortSignal;\n\t} = {}): Promise<AgentConversationData> {\n\t\tconst timeoutMs = options.timeoutMs ?? 120_000;\n\t\tconst intervalMs = options.intervalMs ?? 2_000;\n\t\tconst deadline = Date.now() + timeoutMs;\n\n\t\t// Checked BEFORE the first sleep. A conversation that is already ready\n\t\t// should cost one request, not one request plus an interval.\n\t\tlet data = await this.get();\n\t\twhile (!data.ready) {\n\t\t\tif (options.signal?.aborted) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: 'waitUntilReady was cancelled',\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (Date.now() >= deadline) {\n\t\t\t\t// Name the outstanding REQUIRED items. The optional ones are not why\n\t\t\t\t// this failed and listing them would send someone after the wrong\n\t\t\t\t// thing.\n\t\t\t\tconst outstanding = data.requires\n\t\t\t\t\t.filter((r) => r.required)\n\t\t\t\t\t.map((r) => `${r.kind} ${r.name}`);\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: outstanding.length\n\t\t\t\t\t\t? `Conversation ${this.id} is still waiting on: ${outstanding.join(', ')}`\n\t\t\t\t\t\t: `Conversation ${this.id} did not become ready within ${timeoutMs}ms`,\n\t\t\t\t\tcode: 'conversation_not_ready',\n\t\t\t\t\tstatus: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Never sleep past the deadline, so the timeout means what it says.\n\t\t\tawait sleep(Math.min(intervalMs, Math.max(0, deadline - Date.now())), options.signal);\n\t\t\tdata = await this.get();\n\t\t}\n\t\treturn data;\n\t}\n\n\t// ---------------------------------------------------------------- the drive\n\n\t/** List one directory. `path`, not `dir`: it is the same parameter every other\n\t * file route on this API takes, and one name for one idea beats a prettier\n\t * name used in one place. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: DriveEntry[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []).map(toFileEntry);\n\t}\n\n\t/** Read a text file. */\n\tasync readFile(path: string): Promise<string> {\n\t\t// The route answers JSON `{path, content}`, not bare text. The envelope is\n\t\t// worth keeping on the wire (the canonical path comes back with it), so the\n\t\t// unwrapping happens here: a caller asked for the file, not for a wrapper.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync readFileBytes(path: string): Promise<Uint8Array> {\n\t\tconst res = await this.#http.send(`${this.base}/files/raw`, { query: { path } });\n\t\treturn new Uint8Array(await res.arrayBuffer());\n\t}\n\n\t/**\n\t * Put a file into the conversation's drive.\n\t *\n\t * This is how an application STAGES material: write the contract in, then ask\n\t * the question about it. Anywhere under the root is fine; `input/` is a\n\t * convention in the docs and nothing enforces it, because the agent and your\n\t * application share one namespace.\n\t */\n\tasync writeFile(\n\t\tpath: string,\n\t\tbytes: Uint8Array | ArrayBuffer,\n\t\tcontentType?: string,\n\t): Promise<FileEntry> {\n\t\treturn this.#http.request<FileEntry>(`${this.base}/files`, {\n\t\t\tmethod: 'PUT',\n\t\t\tbytes,\n\t\t\tquery: { path },\n\t\t\t...(contentType && { contentType }),\n\t\t});\n\t}\n\n\tasync removeFile(path: string): Promise<void> {\n\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t}\n\n\t/**\n\t * A link to one file that carries its own authorisation.\n\t *\n\t * A browser cannot hold an API key, so a Download button has no way to prove\n\t * it is allowed. This grants that one file for `ttl` seconds and nothing\n\t * else.\n\t */\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\nlet fallbackTurnSequence = 0;\nfunction newTurnKey(): string {\n\treturn globalThis.crypto?.randomUUID?.() ??\n\t\t`turn-${Date.now().toString(36)}-${++fallbackTurnSequence}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** One entry as the Drive returns it. */\ninterface DriveEntry {\n\tname: string;\n\tpath: string;\n\tisDir: boolean;\n\tsize: number;\n\t/** Epoch milliseconds. */\n\tmtime: number;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n\ttxt: 'text/plain',\n\tmd: 'text/markdown',\n\tjson: 'application/json',\n\tcsv: 'text/csv',\n\thtml: 'text/html',\n\tjs: 'text/javascript',\n\tts: 'text/typescript',\n\tpng: 'image/png',\n\tjpg: 'image/jpeg',\n\tjpeg: 'image/jpeg',\n\tgif: 'image/gif',\n\twebp: 'image/webp',\n\tsvg: 'image/svg+xml',\n\tpdf: 'application/pdf',\n};\n\n/**\n * A Drive entry as the SDK's FileEntry.\n *\n * The Drive speaks `isDir` and `mtime`; the SDK speaks `kind` and an ISO\n * `modifiedAt`. Translating at the edge is the SDK's job: the Drive's shape is\n * what apps/web's DrivePane reads, and changing it to suit this would break a\n * working screen to save a mapping function.\n *\n * `mimeType` is inferred from the extension, because the Drive does not store\n * one (see apps/backend-workers/src/sandbox/drive-reader.ts, which infers it the\n * same way on read).\n */\nfunction toFileEntry(entry: DriveEntry): FileEntry {\n\tconst ext = entry.path.slice(entry.path.lastIndexOf('.') + 1).toLowerCase();\n\treturn {\n\t\tpath: entry.path,\n\t\tkind: entry.isDir ? 'directory' : 'file',\n\t\tmimeType: entry.isDir ? 'inode/directory' : (CONTENT_TYPES[ext] ?? 'application/octet-stream'),\n\t\tsize: entry.size,\n\t\tmodifiedAt: new Date(entry.mtime).toISOString(),\n\t};\n}\n\n/** Wait, and stop waiting early if the caller aborts. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\tif (ms <= 0) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst timer = setTimeout(done, ms);\n\t\tfunction done() {\n\t\t\tclearTimeout(timer);\n\t\t\tsignal?.removeEventListener('abort', done);\n\t\t\tresolve();\n\t\t}\n\t\tsignal?.addEventListener('abort', done, { once: true });\n\t});\n}\n"]}
|
package/index.d.ts
CHANGED
|
@@ -16,4 +16,4 @@ export { loadAgentDir } from './load-agent-dir.ts';
|
|
|
16
16
|
export { loadManagerTemplate } from './templates.ts';
|
|
17
17
|
export type { Turn } from './turn.ts';
|
|
18
18
|
export type { ClientOptions } from './http.ts';
|
|
19
|
-
export type { AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentFields, AgentStatus, Attachment, Bindings, ConnectionStatus, Credits, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, UsedBy, WebsiteChat, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
19
|
+
export type { AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentFields, AgentStatus, Attachment, Bindings, ConnectionStatus, Credits, DatabaseColumn, DatabaseData, DatabaseEngine, DatabaseFields, DatabaseLogin, DatabaseTable, DatabaseTestResult, DatabaseTls, DatabaseUsedBy, FileEntry, Filesystem, Grade, HttpTool, HttpToolParam, McpServer, ProjectAgent, Message, Parameter, ProjectConversationData, ProjectData, ProjectFields, Requirement, Scope, SeatPlan, SecretMeta, SecretRequirement, Step, Trigger, WebhookDelivery, WebhookVerify, TriggerTarget, TurnEvent, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tAgentStatus,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tUsedBy,\n\tWebsiteChat,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,0DAA0D;AAC1D,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,yCAAyC;AACzC,2CAA2C;AAC3C,8BAA8B;AAC9B,8EAA8E;AAC9E,QAAQ;AACR,uEAAuE;AACvE,2EAA2E;AAC3E,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,KAAK,EAAqB,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,2EAA2E;AAC3E,iFAAiF;AACjF,OAAO,EACN,UAAU,GAGV,MAAM,iBAAiB,CAAC;AACzB,6EAA6E;AAC7E,wEAAwE;AACxE,OAAO,EAAE,GAAG,EAAc,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,OAAO,EAA4B,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,2DAA2D;AAC3D,OAAO,EACN,WAAW,EACX,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,GASZ,MAAM,mBAAmB,CAAC;AAC3B,4EAA4E;AAC5E,kEAAkE;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,+EAA+E;AAC/E,6EAA6E;AAC7E,2BAA2B;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["// @cerebro-labs/agentnava-sdk\n//\n// Configure an agent, start a conversation, work with it.\n//\n// import { AgentNava } from '@cerebro-labs/agentnava-sdk';\n//\n// const ws = AgentNava.getWorkspace();\n// const agent = await ws.agents.create({\n// name: 'Refund checker',\n// instructions: 'Decide whether a late delivery qualifies for a refund.',\n// });\n// const conversation = await agent.start({ endUserId: 'u_ananya' });\n// console.log(await conversation.ask('Order 4471 was nine days late.'));\n//\n// The reference this implements is design/sdk-design.md.\n\nexport { AgentNava, Workspace } from './workspace.ts';\nexport { Agent, type StartOptions } from './agent.ts';\nexport { Account } from './account.ts';\n// Builds one entry of an agent's setup: an app, and whose account. NOT the\n// 0.43 `Connection`, which was a handle on a signed-in account and is `Account`.\nexport {\n\tConnection,\n\ttype CreatorAccountConnection,\n\ttype EndUserAccountConnection,\n} from './connection.ts';\n// Every app the platform can connect, as constants: `App.GMAIL === 'gmail'`.\n// Generated from the connections catalog; any plain string still works.\nexport { App, type AppId } from './apps.generated.ts';\nexport { Secret } from './secret.ts';\nexport { EndUser } from './end-user.ts';\nexport { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';\nexport { KnowledgeBase } from './knowledge-base.ts';\nexport { Database } from './database.ts';\nexport { Project, type ProjectStartOptions } from './project.ts';\nexport { AgentNavaError } from './errors.ts';\nexport { loadWorkflows, parseWorkflow } from './workflows.ts';\n// Draft an agent's tools from whatever a company already has: an OpenAPI\n// document, a Postman collection, or a working curl command. Runs entirely on\n// your machine: nothing about the source is sent to AgentNava, which matters\n// because those files routinely contain a live credential.\nexport {\n\timportTools,\n\tfromOpenApi,\n\tfromOpenApiUrl,\n\tfromPostman,\n\tfromCurl,\n\ttoolNameFrom,\n\ttype ImportSource,\n\ttype ImportOptions,\n\ttype ImportedTools,\n\ttype ImportedSecret,\n\ttype ImportWarning,\n\ttype ImportWarningKind,\n\ttype OpenApiDocument,\n\ttype PostmanCollection,\n} from './import-tools.ts';\n// Reads a whole Claude-Code-shaped agent directory (AGENT.md + workflows/ +\n// mcp-servers.json ...) into the fields ws.agents.create() takes.\nexport { loadAgentDir } from './load-agent-dir.ts';\n// The platform's built-in project-manager configuration. apps/api imports this\n// through the './templates' subpath; re-exported here so the package has one\n// surface rather than two.\nexport { loadManagerTemplate } from './templates.ts';\nexport type { Turn } from './turn.ts';\nexport type { ClientOptions } from './http.ts';\nexport type {\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentFields,\n\tAgentStatus,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tDatabaseColumn,\n\tDatabaseData,\n\tDatabaseEngine,\n\tDatabaseFields,\n\tDatabaseLogin,\n\tDatabaseTable,\n\tDatabaseTestResult,\n\tDatabaseTls,\n\tDatabaseUsedBy,\n\tFileEntry,\n\tFilesystem,\n\tGrade,\n\tHttpTool,\n\tHttpToolParam,\n\tMcpServer,\n\tProjectAgent,\n\tMessage,\n\tParameter,\n\tProjectConversationData,\n\tProjectData,\n\tProjectFields,\n\tRequirement,\n\tScope,\n\tSeatPlan,\n\tSecretMeta,\n\tSecretRequirement,\n\tStep,\n\tTrigger,\n\tWebhookDelivery,\n\tWebhookVerify,\n\tTriggerTarget,\n\tTurnEvent,\n\tWidget,\n\tWidgetPart,\n\tUsedBy,\n\tWebsiteChat,\n\tWebsiteChatAccess,\n\tWorkflow,\n\tWorkspaceInfo,\n\tWorkspaceMember,\n} from './types.ts';\n"]}
|
package/load-agent-dir.js
CHANGED
|
@@ -66,8 +66,8 @@ export function loadAgentDir(path, opts) {
|
|
|
66
66
|
workflows.push(asWorkflow(skill));
|
|
67
67
|
}
|
|
68
68
|
// `commands/` is read only to WARN about it. A spec has no commands field any
|
|
69
|
-
// more: slash commands
|
|
70
|
-
//
|
|
69
|
+
// more: no runtime reads slash commands, and none ever had a way to author
|
|
70
|
+
// them in a UI.
|
|
71
71
|
//
|
|
72
72
|
// Deliberately not folded into workflows, which would be the tempting
|
|
73
73
|
// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is
|
|
@@ -88,9 +88,6 @@ export function loadAgentDir(path, opts) {
|
|
|
88
88
|
const httpTools = loadHttpTools(join(path, 'http-tools.json'));
|
|
89
89
|
if (httpTools && httpTools.length > 0)
|
|
90
90
|
out.httpTools = httpTools;
|
|
91
|
-
const widgets = loadWidgets(join(path, 'widgets.json'));
|
|
92
|
-
if (widgets && widgets.length > 0)
|
|
93
|
-
out.widgets = widgets;
|
|
94
91
|
const mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));
|
|
95
92
|
if (mcpServers && mcpServers.length > 0)
|
|
96
93
|
out.mcpServers = mcpServers;
|
|
@@ -149,22 +146,6 @@ function loadHttpTools(filePath) {
|
|
|
149
146
|
}
|
|
150
147
|
return parsed;
|
|
151
148
|
}
|
|
152
|
-
function loadWidgets(filePath) {
|
|
153
|
-
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
154
|
-
return undefined;
|
|
155
|
-
const raw = readFileSync(filePath, 'utf8');
|
|
156
|
-
let parsed;
|
|
157
|
-
try {
|
|
158
|
-
parsed = JSON.parse(raw);
|
|
159
|
-
}
|
|
160
|
-
catch (e) {
|
|
161
|
-
throw new Error(`[loadAgentDir] widgets.json: invalid JSON (${e.message})`);
|
|
162
|
-
}
|
|
163
|
-
if (!Array.isArray(parsed)) {
|
|
164
|
-
throw new Error('[loadAgentDir] widgets.json: must be an array of widget defs');
|
|
165
|
-
}
|
|
166
|
-
return parsed;
|
|
167
|
-
}
|
|
168
149
|
function loadMcpServers(filePath) {
|
|
169
150
|
if (!existsSync(filePath) || !statSync(filePath).isFile())
|
|
170
151
|
return undefined;
|
package/load-agent-dir.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-agent-dir.js","sourceRoot":"","sources":["../src/load-agent-dir.ts"],"names":[],"mappings":"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","sourcesContent":["// `loadAgentDir` reads a Claude-Code-style folder and returns a SpecInput\n// suitable for `an.specs.push(...)`. Layout it expects:\n//\n// <path>/\n// ├── AGENT.md # required: core instructions (CLAUDE.md equivalent)\n// ├── skills/<name>/SKILL.md # optional, repeated: loaded as workflows\n// ├── subagents/<name>.md # optional, repeated (advanced)\n// ├── workflows/<name>.md # optional, repeated\n// ├── commands/<name>.md # IGNORED (warned): specs carry no commands\n// ├── http-tools.json # optional, declarative HTTP tool array\n// └── agent.json # optional, capabilities (tools, sandbox, role)\n//\n// Files inside `skills/` use the Claude Code convention: one folder per skill,\n// containing `SKILL.md` with YAML frontmatter. The folder name is the skill name.\n// Each one loads as a WORKFLOW: a spec is instructions plus workflows and has no\n// skills collection (PR #873). `skills/` is read only so a Claude Code agent\n// directory drops in unchanged.\n// `subagents/` and `workflows/` are flat directories of `<name>.md`. A\n// `commands/` directory is ignored with a warning: specs no longer carry them.\n//\n// Every workflow, from either folder, is read with `parseWorkflow`, so its\n// frontmatter `when:` (or a SKILL.md's `description:`) becomes the `when` the\n// platform requires. The file or folder name stays the workflow's name.\n//\n// `agent.json` is what makes a folder self-describing: it carries the spec-level\n// capabilities (`tools`, `sandbox`, `role`) alongside the markdown, so an agent\n// directory can be copied between workspaces and still be the same agent. Values\n// passed in `opts` win over the file, so a caller can still override in code.\n//\n// Knowledge base files are NOT loaded by this helper. Use `an.knowledge.upload`\n// for those. Everything else in the bundle is markdown.\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Filesystem, HttpTool, McpServer, SpecFile, SpecInput } from './types.ts';\nimport { parseWorkflow, workflowSlug } from './workflows.ts';\n\n// Local aliases, so the body of this file reads the way it was written against\n// the retired package's names. Nothing outside this module uses them.\ntype HttpToolDef = HttpTool;\ntype McpServerSpec = McpServer;\ntype AgentRole = string;\ntype SpecSandbox = Filesystem;\ntype SpecToolGroup = string;\ntype WidgetDef = { name: string; [k: string]: unknown };\n\nexport interface LoadAgentDirOptions {\n\t/** Dev-chosen, stable key, required. Used by the backend to upsert this spec. */\n\tkey: string;\n\t/** The role this agent plays. Default `'freelancer'` is applied server-side\n\t * when omitted. See {@link AgentRole} for the full vocabulary. */\n\trole?: AgentRole;\n\t/** Platform tool groups to grant. Overrides `agent.json`. */\n\ttools?: SpecToolGroup[];\n\t/** Filesystem for this agent's turns. Overrides `agent.json`. */\n\tsandbox?: SpecSandbox;\n}\n\n/** The optional `agent.json` manifest, spec-level capabilities that belong with\n * the markdown rather than in whatever script happens to push it. */\ninterface AgentManifest {\n\ttools?: SpecToolGroup[];\n\tsandbox?: SpecSandbox;\n\trole?: AgentRole;\n}\n\nexport function loadAgentDir(path: string, opts: LoadAgentDirOptions): SpecInput {\n\tif (!opts.key) {\n\t\tthrow new Error('[loadAgentDir] opts.key is required');\n\t}\n\tif (!existsSync(path) || !statSync(path).isDirectory()) {\n\t\tthrow new Error(`[loadAgentDir] not a directory: ${path}`);\n\t}\n\n\tconst agentMdPath = join(path, 'AGENT.md');\n\tif (!existsSync(agentMdPath)) {\n\t\tthrow new Error(\n\t\t\t`[loadAgentDir] missing required file: ${agentMdPath}. ` +\n\t\t\t\t`Every spec needs an AGENT.md (the CLAUDE.md equivalent: core instructions).`,\n\t\t);\n\t}\n\tconst instructions = readFileSync(agentMdPath, 'utf8');\n\n\tconst subagents = loadFlatMdDir(join(path, 'subagents'));\n\t// A workflows/<name>.md wins over a skills/<name>/SKILL.md of the same name,\n\t// \"same\" as the platform compares them (workflowSlug): it refuses two\n\t// workflows that reduce to one identifier, so both would fail at publish.\n\t// Warned rather than hidden in silence, like commands/.\n\tconst asWorkflow = (f: SpecFile): SpecFile => ({ ...parseWorkflow(f.content, f.name), name: f.name });\n\tconst workflows = loadFlatMdDir(join(path, 'workflows')).map(asWorkflow);\n\tconst key = (name: string) => workflowSlug(name) || name;\n\t// slug -> the file that claimed it, so the warning names the right folder.\n\tconst claimed = new Map<string, string>(workflows.map((w) => [key(w.name), `workflows/${w.name}.md`]));\n\tfor (const skill of loadSkillsDir(join(path, 'skills'))) {\n\t\tconst file = `skills/${skill.name}/SKILL.md`;\n\t\tconst shadow = claimed.get(key(skill.name));\n\t\tif (shadow !== undefined) {\n\t\t\tconsole.warn(`[loadAgentDir] ignoring ${file}: ${shadow} has the same name. Rename one of them to keep both.`);\n\t\t\tcontinue;\n\t\t}\n\t\tclaimed.set(key(skill.name), file);\n\t\tworkflows.push(asWorkflow(skill));\n\t}\n\t// `commands/` is read only to WARN about it. A spec has no commands field any\n\t// more: slash commands had one writer (the dedicated-vm translator) and no\n\t// reader on the default runtime.\n\t//\n\t// Deliberately not folded into workflows, which would be the tempting\n\t// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is\n\t// invoked by the model whenever it looks relevant. Folding them would make\n\t// every /command fire unbidden, which is a behaviour change disguised as a\n\t// migration. Warned rather than dropped in silence, and rather than thrown,\n\t// so an existing agent directory still loads.\n\tconst commands = loadFlatMdDir(join(path, 'commands'));\n\tif (commands.length > 0) {\n\t\tconsole.warn(\n\t\t\t`[loadAgentDir] ignoring ${commands.length} file(s) in commands/: agent specs no longer ` +\n\t\t\t\t'carry slash commands. Move any that should run automatically into workflows/.',\n\t\t);\n\t}\n\n\tconst out: SpecInput = { key: opts.key, instructions };\n\tif (subagents.length > 0) out.subagents = subagents;\n\tif (workflows.length > 0) out.workflows = workflows;\n\tconst httpTools = loadHttpTools(join(path, 'http-tools.json'));\n\tif (httpTools && httpTools.length > 0) out.httpTools = httpTools;\n\tconst widgets = loadWidgets(join(path, 'widgets.json'));\n\tif (widgets && widgets.length > 0) out.widgets = widgets;\n\tconst mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));\n\tif (mcpServers && mcpServers.length > 0) out.mcpServers = mcpServers;\n\n\tconst manifest = loadManifest(join(path, 'agent.json'));\n\tconst tools = opts.tools ?? manifest.tools;\n\tif (tools && tools.length > 0) out.tools = tools;\n\tconst sandbox = opts.sandbox ?? manifest.sandbox;\n\tif (sandbox) out.sandbox = sandbox;\n\tconst role = opts.role ?? manifest.role;\n\tif (role) out.role = role;\n\treturn out;\n}\n\n/** agent.json, optional. Absent is the common case and means \"all defaults\". */\nfunction loadManifest(filePath: string): AgentManifest {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return {};\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] agent.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: must be an object');\n\t}\n\tconst m = parsed as Record<string, unknown>;\n\tif (m.tools !== undefined && !Array.isArray(m.tools)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: \"tools\" must be an array');\n\t}\n\tif (m.sandbox !== undefined && m.sandbox !== 'drive' && m.sandbox !== 'none') {\n\t\tthrow new Error(`[loadAgentDir] agent.json: \"sandbox\" must be 'drive' or 'none'`);\n\t}\n\t// Group names are NOT validated here on purpose: the platform is the\n\t// authority on which groups exist and rejects unknown ones at push time with\n\t// the current list, so the SDK never goes stale against a newer platform.\n\treturn m as AgentManifest;\n}\n\nfunction loadHttpTools(filePath: string): HttpToolDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] http-tools.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] http-tools.json: must be an array of tool defs');\n\t}\n\treturn parsed as HttpToolDef[];\n}\n\nfunction loadWidgets(filePath: string): WidgetDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] widgets.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] widgets.json: must be an array of widget defs');\n\t}\n\treturn parsed as WidgetDef[];\n}\n\nfunction loadMcpServers(filePath: string): McpServerSpec[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] mcp-servers.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] mcp-servers.json: must be an array of MCP server defs');\n\t}\n\treturn parsed as McpServerSpec[];\n}\n\n/** skills/<name>/SKILL.md: name comes from the directory. */\nfunction loadSkillsDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const name of readdirSync(dir).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst skillPath = join(dir, name, 'SKILL.md');\n\t\tif (existsSync(skillPath) && statSync(skillPath).isFile()) {\n\t\t\tout.push({ name, content: readFileSync(skillPath, 'utf8') });\n\t\t}\n\t}\n\treturn out;\n}\n\n/** subagents/, workflows/: flat directories of <name>.md. */\nfunction loadFlatMdDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const file of readdirSync(dir).sort()) {\n\t\tif (!file.endsWith('.md') || file.startsWith('.')) continue;\n\t\tconst filePath = join(dir, file);\n\t\tif (!statSync(filePath).isFile()) continue;\n\t\tconst name = file.replace(/\\.md$/, '');\n\t\tout.push({ name, content: readFileSync(filePath, 'utf8') });\n\t}\n\treturn out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-agent-dir.js","sourceRoot":"","sources":["../src/load-agent-dir.ts"],"names":[],"mappings":"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","sourcesContent":["// `loadAgentDir` reads a Claude-Code-style folder and returns a SpecInput\n// suitable for `an.specs.push(...)`. Layout it expects:\n//\n// <path>/\n// ├── AGENT.md # required: core instructions (CLAUDE.md equivalent)\n// ├── skills/<name>/SKILL.md # optional, repeated: loaded as workflows\n// ├── subagents/<name>.md # optional, repeated (advanced)\n// ├── workflows/<name>.md # optional, repeated\n// ├── commands/<name>.md # IGNORED (warned): specs carry no commands\n// ├── http-tools.json # optional, declarative HTTP tool array\n// └── agent.json # optional, capabilities (tools, sandbox, role)\n//\n// Files inside `skills/` use the Claude Code convention: one folder per skill,\n// containing `SKILL.md` with YAML frontmatter. The folder name is the skill name.\n// Each one loads as a WORKFLOW: a spec is instructions plus workflows and has no\n// skills collection (PR #873). `skills/` is read only so a Claude Code agent\n// directory drops in unchanged.\n// `subagents/` and `workflows/` are flat directories of `<name>.md`. A\n// `commands/` directory is ignored with a warning: specs no longer carry them.\n//\n// Every workflow, from either folder, is read with `parseWorkflow`, so its\n// frontmatter `when:` (or a SKILL.md's `description:`) becomes the `when` the\n// platform requires. The file or folder name stays the workflow's name.\n//\n// `agent.json` is what makes a folder self-describing: it carries the spec-level\n// capabilities (`tools`, `sandbox`, `role`) alongside the markdown, so an agent\n// directory can be copied between workspaces and still be the same agent. Values\n// passed in `opts` win over the file, so a caller can still override in code.\n//\n// Knowledge base files are NOT loaded by this helper. Use `an.knowledge.upload`\n// for those. Everything else in the bundle is markdown.\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Filesystem, HttpTool, McpServer, SpecFile, SpecInput } from './types.ts';\nimport { parseWorkflow, workflowSlug } from './workflows.ts';\n\n// Local aliases, so the body of this file reads the way it was written against\n// the retired package's names. Nothing outside this module uses them.\ntype HttpToolDef = HttpTool;\ntype McpServerSpec = McpServer;\ntype AgentRole = string;\ntype SpecSandbox = Filesystem;\ntype SpecToolGroup = string;\n\nexport interface LoadAgentDirOptions {\n\t/** Dev-chosen, stable key, required. Used by the backend to upsert this spec. */\n\tkey: string;\n\t/** The role this agent plays. Default `'freelancer'` is applied server-side\n\t * when omitted. See {@link AgentRole} for the full vocabulary. */\n\trole?: AgentRole;\n\t/** Platform tool groups to grant. Overrides `agent.json`. */\n\ttools?: SpecToolGroup[];\n\t/** Filesystem for this agent's turns. Overrides `agent.json`. */\n\tsandbox?: SpecSandbox;\n}\n\n/** The optional `agent.json` manifest, spec-level capabilities that belong with\n * the markdown rather than in whatever script happens to push it. */\ninterface AgentManifest {\n\ttools?: SpecToolGroup[];\n\tsandbox?: SpecSandbox;\n\trole?: AgentRole;\n}\n\nexport function loadAgentDir(path: string, opts: LoadAgentDirOptions): SpecInput {\n\tif (!opts.key) {\n\t\tthrow new Error('[loadAgentDir] opts.key is required');\n\t}\n\tif (!existsSync(path) || !statSync(path).isDirectory()) {\n\t\tthrow new Error(`[loadAgentDir] not a directory: ${path}`);\n\t}\n\n\tconst agentMdPath = join(path, 'AGENT.md');\n\tif (!existsSync(agentMdPath)) {\n\t\tthrow new Error(\n\t\t\t`[loadAgentDir] missing required file: ${agentMdPath}. ` +\n\t\t\t\t`Every spec needs an AGENT.md (the CLAUDE.md equivalent: core instructions).`,\n\t\t);\n\t}\n\tconst instructions = readFileSync(agentMdPath, 'utf8');\n\n\tconst subagents = loadFlatMdDir(join(path, 'subagents'));\n\t// A workflows/<name>.md wins over a skills/<name>/SKILL.md of the same name,\n\t// \"same\" as the platform compares them (workflowSlug): it refuses two\n\t// workflows that reduce to one identifier, so both would fail at publish.\n\t// Warned rather than hidden in silence, like commands/.\n\tconst asWorkflow = (f: SpecFile): SpecFile => ({ ...parseWorkflow(f.content, f.name), name: f.name });\n\tconst workflows = loadFlatMdDir(join(path, 'workflows')).map(asWorkflow);\n\tconst key = (name: string) => workflowSlug(name) || name;\n\t// slug -> the file that claimed it, so the warning names the right folder.\n\tconst claimed = new Map<string, string>(workflows.map((w) => [key(w.name), `workflows/${w.name}.md`]));\n\tfor (const skill of loadSkillsDir(join(path, 'skills'))) {\n\t\tconst file = `skills/${skill.name}/SKILL.md`;\n\t\tconst shadow = claimed.get(key(skill.name));\n\t\tif (shadow !== undefined) {\n\t\t\tconsole.warn(`[loadAgentDir] ignoring ${file}: ${shadow} has the same name. Rename one of them to keep both.`);\n\t\t\tcontinue;\n\t\t}\n\t\tclaimed.set(key(skill.name), file);\n\t\tworkflows.push(asWorkflow(skill));\n\t}\n\t// `commands/` is read only to WARN about it. A spec has no commands field any\n\t// more: no runtime reads slash commands, and none ever had a way to author\n\t// them in a UI.\n\t//\n\t// Deliberately not folded into workflows, which would be the tempting\n\t// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is\n\t// invoked by the model whenever it looks relevant. Folding them would make\n\t// every /command fire unbidden, which is a behaviour change disguised as a\n\t// migration. Warned rather than dropped in silence, and rather than thrown,\n\t// so an existing agent directory still loads.\n\tconst commands = loadFlatMdDir(join(path, 'commands'));\n\tif (commands.length > 0) {\n\t\tconsole.warn(\n\t\t\t`[loadAgentDir] ignoring ${commands.length} file(s) in commands/: agent specs no longer ` +\n\t\t\t\t'carry slash commands. Move any that should run automatically into workflows/.',\n\t\t);\n\t}\n\n\tconst out: SpecInput = { key: opts.key, instructions };\n\tif (subagents.length > 0) out.subagents = subagents;\n\tif (workflows.length > 0) out.workflows = workflows;\n\tconst httpTools = loadHttpTools(join(path, 'http-tools.json'));\n\tif (httpTools && httpTools.length > 0) out.httpTools = httpTools;\n\tconst mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));\n\tif (mcpServers && mcpServers.length > 0) out.mcpServers = mcpServers;\n\n\tconst manifest = loadManifest(join(path, 'agent.json'));\n\tconst tools = opts.tools ?? manifest.tools;\n\tif (tools && tools.length > 0) out.tools = tools;\n\tconst sandbox = opts.sandbox ?? manifest.sandbox;\n\tif (sandbox) out.sandbox = sandbox;\n\tconst role = opts.role ?? manifest.role;\n\tif (role) out.role = role;\n\treturn out;\n}\n\n/** agent.json, optional. Absent is the common case and means \"all defaults\". */\nfunction loadManifest(filePath: string): AgentManifest {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return {};\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] agent.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: must be an object');\n\t}\n\tconst m = parsed as Record<string, unknown>;\n\tif (m.tools !== undefined && !Array.isArray(m.tools)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: \"tools\" must be an array');\n\t}\n\tif (m.sandbox !== undefined && m.sandbox !== 'drive' && m.sandbox !== 'none') {\n\t\tthrow new Error(`[loadAgentDir] agent.json: \"sandbox\" must be 'drive' or 'none'`);\n\t}\n\t// Group names are NOT validated here on purpose: the platform is the\n\t// authority on which groups exist and rejects unknown ones at push time with\n\t// the current list, so the SDK never goes stale against a newer platform.\n\treturn m as AgentManifest;\n}\n\nfunction loadHttpTools(filePath: string): HttpToolDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] http-tools.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] http-tools.json: must be an array of tool defs');\n\t}\n\treturn parsed as HttpToolDef[];\n}\n\nfunction loadMcpServers(filePath: string): McpServerSpec[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] mcp-servers.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] mcp-servers.json: must be an array of MCP server defs');\n\t}\n\treturn parsed as McpServerSpec[];\n}\n\n/** skills/<name>/SKILL.md: name comes from the directory. */\nfunction loadSkillsDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const name of readdirSync(dir).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst skillPath = join(dir, name, 'SKILL.md');\n\t\tif (existsSync(skillPath) && statSync(skillPath).isFile()) {\n\t\t\tout.push({ name, content: readFileSync(skillPath, 'utf8') });\n\t\t}\n\t}\n\treturn out;\n}\n\n/** subagents/, workflows/: flat directories of <name>.md. */\nfunction loadFlatMdDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const file of readdirSync(dir).sort()) {\n\t\tif (!file.endsWith('.md') || file.startsWith('.')) continue;\n\t\tconst filePath = join(dir, file);\n\t\tif (!statSync(filePath).isFile()) continue;\n\t\tconst name = file.replace(/\\.md$/, '');\n\t\tout.push({ name, content: readFileSync(filePath, 'utf8') });\n\t}\n\treturn out;\n}\n"]}
|
package/package.json
CHANGED
package/project.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Conversation } from './conversation.ts';
|
|
2
2
|
import type { Http } from './http.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type FileEntry, type ProjectData, type ProjectFields } from './types.ts';
|
|
4
4
|
export interface ProjectStartOptions {
|
|
5
5
|
parameters?: Record<string, string | number>;
|
|
6
6
|
}
|
package/project.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// A project: several agents, one goal, one shared folder.
|
|
2
2
|
import { Conversation } from "./conversation.js";
|
|
3
|
+
import { GRADE_NAMES } from "./types.js";
|
|
3
4
|
/**
|
|
4
5
|
* A project you can work with. Holding one makes no request.
|
|
5
6
|
*
|
|
@@ -128,6 +129,12 @@ export class Project {
|
|
|
128
129
|
return res.url;
|
|
129
130
|
}
|
|
130
131
|
}
|
|
132
|
+
/** A grade as the server reported it, in this SDK's vocabulary. */
|
|
133
|
+
function readGrade(value) {
|
|
134
|
+
if (value === 'fast')
|
|
135
|
+
return 'standard';
|
|
136
|
+
return GRADE_NAMES.includes(value ?? '') ? value : 'lite';
|
|
137
|
+
}
|
|
131
138
|
/** The `{ project, agents }` envelope as one flat ProjectData.
|
|
132
139
|
*
|
|
133
140
|
* The envelope and the snake_case are what apps/console reads, so the wire
|
|
@@ -141,7 +148,10 @@ export function toProjectData(body) {
|
|
|
141
148
|
name: p?.name ?? '',
|
|
142
149
|
goal: p?.goal ?? null,
|
|
143
150
|
managerInstructions: p?.manager_instructions ?? null,
|
|
144
|
-
|
|
151
|
+
// Any real grade passes through, and the old name `fast` reads as what it
|
|
152
|
+
// means now (`standard`). Anything else is the cheapest, `lite`: guessing
|
|
153
|
+
// upward would tell a caller they pay more than they do.
|
|
154
|
+
grade: readGrade(p?.grade),
|
|
145
155
|
status: p?.status === 'archived' ? 'archived' : 'active',
|
|
146
156
|
agents: members.map((m) => ({
|
|
147
157
|
agentId: m.agent_id,
|
package/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["// A project: several agents, one goal, one shared folder.\n\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport type { FileEntry, ProjectConversationData, ProjectData, ProjectFields } from './types.ts';\n\nexport interface ProjectStartOptions {\n\tparameters?: Record<string, string | number>;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\tttl?: number;\n}\n\n/**\n * A project you can work with. Holding one makes no request.\n *\n * The difference from an agent is the shared folder: every agent on a project\n * reads and writes the SAME files, which is what lets one pick up where another\n * left off. Two conversations with the same agent share nothing; two agents on\n * the same project share everything in that folder.\n */\nexport class Project {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: ProjectData;\n\n\tconstructor(http: Http, id: string, snapshot?: ProjectData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/projects/${encodeURIComponent(this.id)}`;\n\t}\n\n\tget data(): ProjectData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(): Promise<ProjectData> {\n\t\tconst data = toProjectData(await this.#http.get(this.base));\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\tasync update(fields: ProjectFields): Promise<Project> {\n\t\tawait this.#http.patch(this.base, fields);\n\t\t// PATCH answers the row it wrote; re-read so the object carries the roster\n\t\t// too, which the caller will ask for next.\n\t\treturn new Project(this.#http, this.id, await this.get());\n\t}\n\n\tasync addAgent(agentId: string): Promise<Project> {\n\t\tconst data = toProjectData(await this.#http.post(`${this.base}/agents`, { agentId }));\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\tasync removeAgent(agentId: string): Promise<Project> {\n\t\tconst data = toProjectData(\n\t\t\tawait this.#http.delete(`${this.base}/agents/${encodeURIComponent(agentId)}`),\n\t\t);\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\t/** Stop it without destroying it. The folder and the history stay readable. */\n\tasync archive(): Promise<Project> {\n\t\tconst data = toProjectData(await this.#http.post(`${this.base}/archive`));\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\t/** Delete it, and the shared folder with it. Archive is usually what you\n\t * wanted. */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\tasync start(options: ProjectStartOptions = {}): Promise<Conversation> {\n\t\tconst data = await this.#http.post<ProjectConversationData>(`${this.base}/conversations`, {\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t});\n\t\t// A project conversation and an agent conversation differ in what STARTED\n\t\t// them, not in what you can do with one, so they share the class. The\n\t\t// distinction that matters (which id it carries) is in the data.\n\t\treturn new Conversation(this.#http, data.id);\n\t}\n\n\t/**\n\t * Address one of this project's conversations by id. No request.\n\t *\n\t * The sibling of `agent.conversation(id)`: the thing that owns it is the\n\t * thing you ask.\n\t *\n\t * A PROJECT HAS ONE CONVERSATION PER AGENT, NOT ONE IN TOTAL. Starting a\n\t * project opens the manager's conversation and one for every agent on the\n\t * roster, so `conversations()` on a two-agent project returns three.\n\t *\n\t * This comment used to say a project had exactly one, which was true when it\n\t * was written and had stopped being true by the time anyone read it.\n\t * Observed on deployed staging on 2026-09-18: starting a project with Sal and\n\t * Otis returned three, the manager's being the one with no `agent_id`.\n\t *\n\t * Tell them apart by `agent_id` on the wire row: absent means the manager,\n\t * which is derived from the project rather than configured as an agent, and\n\t * whose `conversation.agentId` reads `manager-<projectId>`.\n\t *\n\t * It resolves because `getSession` reads both session tables: an id does\n\t * not say which one it came from, so the route answers for either.\n\t */\n\tconversation(conversationId: string): Conversation {\n\t\treturn new Conversation(this.#http, conversationId);\n\t}\n\n\tasync conversations(): Promise<Conversation[]> {\n\t\t// `sessions`, which is what the route actually answers with. This read\n\t\t// `res.conversations`, which is always undefined, so `?? []` turned a SHAPE\n\t\t// MISMATCH into a plausible empty list and every project reported having no\n\t\t// conversations however many it had.\n\t\t//\n\t\t// The URL was never the problem: /conversations is a real alias for\n\t\t// /sessions and both return `{ sessions: [...] }`. Only the key was wrong,\n\t\t// which is why nothing 404'd and nothing looked broken.\n\t\t//\n\t\t// Same shape as the bugs this SDK's own end-to-end suite found on the\n\t\t// server today: an absence read as an emptiness. `?? []` is the line that\n\t\t// does the hiding, and it is load-bearing for a genuinely empty project, so\n\t\t// the fix is to read the right key rather than to remove the fallback.\n\t\tconst res = await this.#http.get<{ sessions: ProjectConversationData[] }>(\n\t\t\t`${this.base}/conversations`,\n\t\t);\n\t\treturn (res.sessions ?? []).map((c) => new Conversation(this.#http, c.id));\n\t}\n\n\t// --------------------------------------------------------- the shared folder\n\n\t/** List one directory of the shared folder. Same `path` parameter as\n\t * everywhere else on this API. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: unknown[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []) as FileEntry[];\n\t}\n\n\tasync readFile(path: string): Promise<string> {\n\t\t// JSON `{path, content}`, not bare text. See Conversation.readFile.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\n/** A project row as `/v1/projects` returns it: snake_case, nested. */\ninterface WireProject {\n\tid: string;\n\tworkspace_id: string;\n\tname: string;\n\tstatus: string;\n\tgoal: string | null;\n\tmanager_instructions: string | null;\n\tgrade: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\ninterface WireMember {\n\tproject_id: string;\n\tagent_id: string;\n\tagent_version: number | null;\n\tagent_name: string | null;\n\tadded_at: string;\n}\n\n/** The `{ project, agents }` envelope as one flat ProjectData.\n *\n * The envelope and the snake_case are what apps/console reads, so the wire\n * stays and the SDK translates. A caller wants `project.agents`, not\n * `response.agents` alongside `response.project`. */\nexport function toProjectData(\n\tbody: { project?: WireProject; agents?: WireMember[] } | WireProject,\n): ProjectData {\n\tconst p = ('project' in body ? body.project : body) as WireProject;\n\tconst members = ('agents' in body ? (body.agents ?? []) : []) as WireMember[];\n\treturn {\n\t\tid: p?.id ?? '',\n\t\tname: p?.name ?? '',\n\t\tgoal: p?.goal ?? null,\n\t\tmanagerInstructions: p?.manager_instructions ?? null,\n\t\tgrade: (p?.grade === 'premium' ? 'premium' : 'standard'),\n\t\tstatus: p?.status === 'archived' ? 'archived' : 'active',\n\t\tagents: members.map((m) => ({\n\t\t\tagentId: m.agent_id,\n\t\t\tname: m.agent_name ?? '',\n\t\t\taddedAt: m.added_at,\n\t\t})),\n\t\tcreatedAt: p?.created_at ?? '',\n\t\tupdatedAt: p?.updated_at ?? '',\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"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","sourcesContent":["// A project: several agents, one goal, one shared folder.\n\nimport { Conversation } from './conversation.ts';\nimport type { Http } from './http.ts';\nimport { GRADE_NAMES, type FileEntry, type Grade, type ProjectConversationData, type ProjectData, type ProjectFields } from './types.ts';\n\nexport interface ProjectStartOptions {\n\tparameters?: Record<string, string | number>;\n}\n\nexport interface FileUrlOptions {\n\tdownload?: boolean;\n\tttl?: number;\n}\n\n/**\n * A project you can work with. Holding one makes no request.\n *\n * The difference from an agent is the shared folder: every agent on a project\n * reads and writes the SAME files, which is what lets one pick up where another\n * left off. Two conversations with the same agent share nothing; two agents on\n * the same project share everything in that folder.\n */\nexport class Project {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: ProjectData;\n\n\tconstructor(http: Http, id: string, snapshot?: ProjectData) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/projects/${encodeURIComponent(this.id)}`;\n\t}\n\n\tget data(): ProjectData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\tasync get(): Promise<ProjectData> {\n\t\tconst data = toProjectData(await this.#http.get(this.base));\n\t\tthis.snapshot = data;\n\t\treturn data;\n\t}\n\n\tasync update(fields: ProjectFields): Promise<Project> {\n\t\tawait this.#http.patch(this.base, fields);\n\t\t// PATCH answers the row it wrote; re-read so the object carries the roster\n\t\t// too, which the caller will ask for next.\n\t\treturn new Project(this.#http, this.id, await this.get());\n\t}\n\n\tasync addAgent(agentId: string): Promise<Project> {\n\t\tconst data = toProjectData(await this.#http.post(`${this.base}/agents`, { agentId }));\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\tasync removeAgent(agentId: string): Promise<Project> {\n\t\tconst data = toProjectData(\n\t\t\tawait this.#http.delete(`${this.base}/agents/${encodeURIComponent(agentId)}`),\n\t\t);\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\t/** Stop it without destroying it. The folder and the history stay readable. */\n\tasync archive(): Promise<Project> {\n\t\tconst data = toProjectData(await this.#http.post(`${this.base}/archive`));\n\t\tthis.snapshot = data;\n\t\treturn new Project(this.#http, this.id, data);\n\t}\n\n\t/** Delete it, and the shared folder with it. Archive is usually what you\n\t * wanted. */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\tasync start(options: ProjectStartOptions = {}): Promise<Conversation> {\n\t\tconst data = await this.#http.post<ProjectConversationData>(`${this.base}/conversations`, {\n\t\t\t...(options.parameters && { parameters: options.parameters }),\n\t\t});\n\t\t// A project conversation and an agent conversation differ in what STARTED\n\t\t// them, not in what you can do with one, so they share the class. The\n\t\t// distinction that matters (which id it carries) is in the data.\n\t\treturn new Conversation(this.#http, data.id);\n\t}\n\n\t/**\n\t * Address one of this project's conversations by id. No request.\n\t *\n\t * The sibling of `agent.conversation(id)`: the thing that owns it is the\n\t * thing you ask.\n\t *\n\t * A PROJECT HAS ONE CONVERSATION PER AGENT, NOT ONE IN TOTAL. Starting a\n\t * project opens the manager's conversation and one for every agent on the\n\t * roster, so `conversations()` on a two-agent project returns three.\n\t *\n\t * This comment used to say a project had exactly one, which was true when it\n\t * was written and had stopped being true by the time anyone read it.\n\t * Observed on deployed staging on 2026-09-18: starting a project with Sal and\n\t * Otis returned three, the manager's being the one with no `agent_id`.\n\t *\n\t * Tell them apart by `agent_id` on the wire row: absent means the manager,\n\t * which is derived from the project rather than configured as an agent, and\n\t * whose `conversation.agentId` reads `manager-<projectId>`.\n\t *\n\t * It resolves because `getSession` reads both session tables: an id does\n\t * not say which one it came from, so the route answers for either.\n\t */\n\tconversation(conversationId: string): Conversation {\n\t\treturn new Conversation(this.#http, conversationId);\n\t}\n\n\tasync conversations(): Promise<Conversation[]> {\n\t\t// `sessions`, which is what the route actually answers with. This read\n\t\t// `res.conversations`, which is always undefined, so `?? []` turned a SHAPE\n\t\t// MISMATCH into a plausible empty list and every project reported having no\n\t\t// conversations however many it had.\n\t\t//\n\t\t// The URL was never the problem: /conversations is a real alias for\n\t\t// /sessions and both return `{ sessions: [...] }`. Only the key was wrong,\n\t\t// which is why nothing 404'd and nothing looked broken.\n\t\t//\n\t\t// Same shape as the bugs this SDK's own end-to-end suite found on the\n\t\t// server today: an absence read as an emptiness. `?? []` is the line that\n\t\t// does the hiding, and it is load-bearing for a genuinely empty project, so\n\t\t// the fix is to read the right key rather than to remove the fallback.\n\t\tconst res = await this.#http.get<{ sessions: ProjectConversationData[] }>(\n\t\t\t`${this.base}/conversations`,\n\t\t);\n\t\treturn (res.sessions ?? []).map((c) => new Conversation(this.#http, c.id));\n\t}\n\n\t// --------------------------------------------------------- the shared folder\n\n\t/** List one directory of the shared folder. Same `path` parameter as\n\t * everywhere else on this API. */\n\tasync files(path = '/'): Promise<FileEntry[]> {\n\t\tconst res = await this.#http.get<{ files: unknown[] }>(`${this.base}/files`, { path });\n\t\treturn (res.files ?? []) as FileEntry[];\n\t}\n\n\tasync readFile(path: string): Promise<string> {\n\t\t// JSON `{path, content}`, not bare text. See Conversation.readFile.\n\t\tconst res = await this.#http.get<{ path: string; content: string }>(\n\t\t\t`${this.base}/files/content`,\n\t\t\t{ path },\n\t\t);\n\t\treturn res.content ?? '';\n\t}\n\n\tasync fileUrl(path: string, options: FileUrlOptions = {}): Promise<string> {\n\t\tconst res = await this.#http.post<{ url: string }>(`${this.base}/files/url`, {\n\t\t\tpath,\n\t\t\t...(options.download !== undefined && { download: options.download }),\n\t\t\t...(options.ttl !== undefined && { ttl: options.ttl }),\n\t\t});\n\t\treturn res.url;\n\t}\n}\n\n/** A project row as `/v1/projects` returns it: snake_case, nested. */\ninterface WireProject {\n\tid: string;\n\tworkspace_id: string;\n\tname: string;\n\tstatus: string;\n\tgoal: string | null;\n\tmanager_instructions: string | null;\n\tgrade: string | null;\n\tcreated_at: string;\n\tupdated_at: string;\n}\n\ninterface WireMember {\n\tproject_id: string;\n\tagent_id: string;\n\tagent_version: number | null;\n\tagent_name: string | null;\n\tadded_at: string;\n}\n\n/** A grade as the server reported it, in this SDK's vocabulary. */\nfunction readGrade(value: string | null | undefined): Grade {\n\tif (value === 'fast') return 'standard';\n\treturn (GRADE_NAMES as readonly string[]).includes(value ?? '') ? (value as Grade) : 'lite';\n}\n\n/** The `{ project, agents }` envelope as one flat ProjectData.\n *\n * The envelope and the snake_case are what apps/console reads, so the wire\n * stays and the SDK translates. A caller wants `project.agents`, not\n * `response.agents` alongside `response.project`. */\nexport function toProjectData(\n\tbody: { project?: WireProject; agents?: WireMember[] } | WireProject,\n): ProjectData {\n\tconst p = ('project' in body ? body.project : body) as WireProject;\n\tconst members = ('agents' in body ? (body.agents ?? []) : []) as WireMember[];\n\treturn {\n\t\tid: p?.id ?? '',\n\t\tname: p?.name ?? '',\n\t\tgoal: p?.goal ?? null,\n\t\tmanagerInstructions: p?.manager_instructions ?? null,\n\t\t// Any real grade passes through, and the old name `fast` reads as what it\n\t\t// means now (`standard`). Anything else is the cheapest, `lite`: guessing\n\t\t// upward would tell a caller they pay more than they do.\n\t\tgrade: readGrade(p?.grade),\n\t\tstatus: p?.status === 'archived' ? 'archived' : 'active',\n\t\tagents: members.map((m) => ({\n\t\t\tagentId: m.agent_id,\n\t\t\tname: m.agent_name ?? '',\n\t\t\taddedAt: m.added_at,\n\t\t})),\n\t\tcreatedAt: p?.created_at ?? '',\n\t\tupdatedAt: p?.updated_at ?? '',\n\t};\n}\n"]}
|
package/turn.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export type Turn = Promise<string> & AsyncIterable<TurnEvent>;
|
|
|
6
6
|
* Frames are `data: {json}` separated by blank lines. Anything else on the wire
|
|
7
7
|
* (comments, retry directives, a stray blank) is ignored rather than treated as
|
|
8
8
|
* an error: a proxy is allowed to insert keep-alive comments and a stream that
|
|
9
|
-
* died because of one would be maddening to debug.
|
|
10
|
-
|
|
9
|
+
* died because of one would be maddening to debug.
|
|
10
|
+
*
|
|
11
|
+
* `turnId` is the turn the API named in its X-Turn-ID header. It becomes the
|
|
12
|
+
* first event, `start`, yielded inside the same cleanup as the rest, so a caller
|
|
13
|
+
* who stops right there still releases the connection. */
|
|
14
|
+
export declare function parseSSE(body: ReadableStream<Uint8Array>, turnId?: string | null): AsyncGenerator<TurnEvent>;
|
|
11
15
|
export declare function makeTurn(response: Promise<Response>, recoveryMessage?: string): Turn;
|