@cerebro-labs/agentnava-sdk 0.51.0 → 0.74.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 +3 -2
- package/agent.d.ts +67 -9
- package/agent.js +78 -9
- package/agent.js.map +1 -1
- package/attach.d.ts +3 -0
- package/attach.js.map +1 -1
- package/connection.d.ts +10 -0
- package/connection.js +13 -2
- package/connection.js.map +1 -1
- package/conversation.d.ts +37 -1
- package/conversation.js +28 -2
- package/conversation.js.map +1 -1
- package/end-user.d.ts +10 -0
- package/end-user.js +7 -0
- package/end-user.js.map +1 -1
- package/errors.d.ts +20 -0
- package/errors.js +24 -0
- package/errors.js.map +1 -1
- package/http.d.ts +4 -1
- package/http.js +21 -9
- package/http.js.map +1 -1
- package/import-tools.d.ts +5 -0
- package/import-tools.js +47 -6
- package/import-tools.js.map +1 -1
- package/index.d.ts +4 -3
- package/index.js +2 -1
- package/index.js.map +1 -1
- package/knowledge-base.d.ts +18 -18
- package/knowledge-base.js +28 -0
- package/knowledge-base.js.map +1 -1
- package/package.json +1 -1
- package/partner-app.d.ts +70 -0
- package/partner-app.js +125 -0
- package/partner-app.js.map +1 -0
- package/project.d.ts +5 -1
- package/project.js +3 -3
- package/project.js.map +1 -1
- package/react.css +32 -1
- package/react.js +16 -4
- package/react.js.map +1 -1
- package/secret.d.ts +6 -0
- package/secret.js +15 -0
- package/secret.js.map +1 -1
- package/turn.js +21 -0
- package/turn.js.map +1 -1
- package/types.d.ts +262 -3
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/workspace.d.ts +35 -1
- package/workspace.js +35 -0
- package/workspace.js.map +1 -1
package/index.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
export { AgentNava, Workspace } from './workspace.ts';
|
|
2
|
+
export { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.ts';
|
|
2
3
|
export { Agent, type StartOptions } from './agent.ts';
|
|
3
4
|
export { Account } from './account.ts';
|
|
4
5
|
export { Connection, type CreatorAccountConnection, type EndUserAccountConnection, } from './connection.ts';
|
|
5
6
|
export { App, type AppId } from './apps.generated.ts';
|
|
6
7
|
export { Secret } from './secret.ts';
|
|
7
8
|
export { EndUser } from './end-user.ts';
|
|
8
|
-
export { Conversation, type AskOptions, type FileUrlOptions } from './conversation.ts';
|
|
9
|
+
export { Conversation, type AskOptions, type AttachOptions, type FileUrlOptions } from './conversation.ts';
|
|
9
10
|
export { KnowledgeBase } from './knowledge-base.ts';
|
|
10
11
|
export { Database } from './database.ts';
|
|
11
12
|
export { Project, type ProjectStartOptions } from './project.ts';
|
|
12
|
-
export { AgentNavaError } from './errors.ts';
|
|
13
|
+
export { AgentNavaError, SecretHostNotAllowed } from './errors.ts';
|
|
13
14
|
export { loadWorkflows, parseWorkflow } from './workflows.ts';
|
|
14
15
|
export { importTools, fromOpenApi, fromOpenApiUrl, fromPostman, fromCurl, toolNameFrom, type ImportSource, type ImportOptions, type ImportedTools, type ImportedSecret, type ImportWarning, type ImportWarningKind, type OpenApiDocument, type PostmanCollection, } from './import-tools.ts';
|
|
15
16
|
export { loadAgentDir } from './load-agent-dir.ts';
|
|
16
17
|
export { loadManagerTemplate } from './templates.ts';
|
|
17
18
|
export type { Turn } from './turn.ts';
|
|
18
19
|
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, Widget, WidgetPart, UsedBy, WebsiteChat, WebsiteChatAccess, Workflow, WorkspaceInfo, WorkspaceMember, } from './types.ts';
|
|
20
|
+
export type { AppTool, AccountData, AccountNeeded, AccountStatus, AgentConnection, AgentConversationData, AgentData, AgentDraft, AgentFields, AgentStatus, WebsiteChatAppearance, Attachment, Bindings, ConnectionStatus, Credits, Guardrails, 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';
|
|
20
21
|
export { placeWidgets, type ReplyPiece } from './widgets.ts';
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// The reference this implements is design/sdk-design.md.
|
|
16
16
|
export { AgentNava, Workspace } from "./workspace.js";
|
|
17
|
+
export { PartnerApp } from "./partner-app.js";
|
|
17
18
|
export { Agent } from "./agent.js";
|
|
18
19
|
export { Account } from "./account.js";
|
|
19
20
|
// Builds one entry of an agent's setup: an app, and whose account. NOT the
|
|
@@ -28,7 +29,7 @@ export { Conversation } from "./conversation.js";
|
|
|
28
29
|
export { KnowledgeBase } from "./knowledge-base.js";
|
|
29
30
|
export { Database } from "./database.js";
|
|
30
31
|
export { Project } from "./project.js";
|
|
31
|
-
export { AgentNavaError } from "./errors.js";
|
|
32
|
+
export { AgentNavaError, SecretHostNotAllowed } from "./errors.js";
|
|
32
33
|
export { loadWorkflows, parseWorkflow } from "./workflows.js";
|
|
33
34
|
// Draft an agent's tools from whatever a company already has: an OpenAPI
|
|
34
35
|
// document, a Postman collection, or a working curl command. Runs entirely on
|
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,
|
|
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,UAAU,EAAgE,MAAM,kBAAkB,CAAC;AAC5G,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,EAA4D,MAAM,mBAAmB,CAAC;AAC3G,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,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,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;AA6DrD,OAAO,EAAE,YAAY,EAAmB,MAAM,cAAc,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 { PartnerApp, type PartnerAppOptions, type ExchangedCode, type CreditGrant } from './partner-app.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 AttachOptions, 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, SecretHostNotAllowed } 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\tAppTool,\n\tAccountData,\n\tAccountNeeded,\n\tAccountStatus,\n\tAgentConnection,\n\tAgentConversationData,\n\tAgentData,\n\tAgentDraft,\n\tAgentFields,\n\tAgentStatus,\n\tWebsiteChatAppearance,\n\tAttachment,\n\tBindings,\n\tConnectionStatus,\n\tCredits,\n\tGuardrails,\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';\nexport { placeWidgets, type ReplyPiece } from './widgets.ts';\n"]}
|
package/knowledge-base.d.ts
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import type { Http } from './http.ts';
|
|
2
2
|
import type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';
|
|
3
|
-
/**
|
|
4
|
-
* A knowledge base: a named folder of files your agents read.
|
|
5
|
-
*
|
|
6
|
-
* A base lives on the WORKSPACE, not on one agent, and an agent configuration
|
|
7
|
-
* attaches it by key. Two agents attaching the same base is the point: correct a
|
|
8
|
-
* file once and both see the correction.
|
|
9
|
-
*
|
|
10
|
-
* Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session
|
|
11
|
-
* starts, so the agent finds things the way a person would, with `ls`, `grep`
|
|
12
|
-
* and `cat`. There is no search endpoint and no embedding step, deliberately;
|
|
13
|
-
* see the NO EMBEDDINGS decision in CLAUDE.md.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });
|
|
17
|
-
* await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');
|
|
18
|
-
* await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
3
|
export declare class KnowledgeBase {
|
|
22
4
|
#private;
|
|
23
5
|
/** Workspace-unique, and what an agent configuration stores. Not editable:
|
|
@@ -106,6 +88,24 @@ export declare class KnowledgeBase {
|
|
|
106
88
|
* finish.
|
|
107
89
|
*/
|
|
108
90
|
retry: (path: string) => Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Make the base hold exactly these files: write each one, then remove
|
|
93
|
+
* every file not among them.
|
|
94
|
+
*
|
|
95
|
+
* For an app that keeps a base in step with a source it reads again (a
|
|
96
|
+
* website, a store): a page the source dropped leaves the base too.
|
|
97
|
+
* Paths are compared the way the API stores them, with a leading slash,
|
|
98
|
+
* so `pages/home.md` and `/pages/home.md` are the same file. Comparing
|
|
99
|
+
* them as written once removed every file a sync had just written.
|
|
100
|
+
*/
|
|
101
|
+
mirror: (files: {
|
|
102
|
+
path: string;
|
|
103
|
+
text: string;
|
|
104
|
+
contentType?: string;
|
|
105
|
+
}[]) => Promise<{
|
|
106
|
+
written: number;
|
|
107
|
+
removed: number;
|
|
108
|
+
}>;
|
|
109
109
|
/**
|
|
110
110
|
* Upload a whole directory, preserving its structure under `prefix`.
|
|
111
111
|
*
|
package/knowledge-base.js
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
+
/** A file's path as the API stores it: always with one leading slash. */
|
|
20
|
+
function storedPath(path) {
|
|
21
|
+
return `/${path.replace(/^\/+/, '')}`;
|
|
22
|
+
}
|
|
19
23
|
export class KnowledgeBase {
|
|
20
24
|
/** Workspace-unique, and what an agent configuration stores. Not editable:
|
|
21
25
|
* renaming it would move every attachment silently. */
|
|
@@ -150,6 +154,30 @@ export class KnowledgeBase {
|
|
|
150
154
|
retry: async (path) => {
|
|
151
155
|
await this.#http.post(`${this.base}/files/retry`, { path });
|
|
152
156
|
},
|
|
157
|
+
/**
|
|
158
|
+
* Make the base hold exactly these files: write each one, then remove
|
|
159
|
+
* every file not among them.
|
|
160
|
+
*
|
|
161
|
+
* For an app that keeps a base in step with a source it reads again (a
|
|
162
|
+
* website, a store): a page the source dropped leaves the base too.
|
|
163
|
+
* Paths are compared the way the API stores them, with a leading slash,
|
|
164
|
+
* so `pages/home.md` and `/pages/home.md` are the same file. Comparing
|
|
165
|
+
* them as written once removed every file a sync had just written.
|
|
166
|
+
*/
|
|
167
|
+
mirror: async (files) => {
|
|
168
|
+
const wanted = new Set(files.map((f) => storedPath(f.path)));
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
await this.files.put(file.path, new TextEncoder().encode(file.text), file.contentType ?? 'text/markdown');
|
|
171
|
+
}
|
|
172
|
+
let removed = 0;
|
|
173
|
+
for (const entry of await this.files.list()) {
|
|
174
|
+
if (wanted.has(storedPath(entry.path)))
|
|
175
|
+
continue;
|
|
176
|
+
await this.files.remove(entry.path);
|
|
177
|
+
removed++;
|
|
178
|
+
}
|
|
179
|
+
return { written: files.length, removed };
|
|
180
|
+
},
|
|
153
181
|
/**
|
|
154
182
|
* Upload a whole directory, preserving its structure under `prefix`.
|
|
155
183
|
*
|
package/knowledge-base.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-base.js","sourceRoot":"","sources":["../src/knowledge-base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Http } from './http.ts';\nimport type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';\n\n/**\n * A knowledge base: a named folder of files your agents read.\n *\n * A base lives on the WORKSPACE, not on one agent, and an agent configuration\n * attaches it by key. Two agents attaching the same base is the point: correct a\n * file once and both see the correction.\n *\n * Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session\n * starts, so the agent finds things the way a person would, with `ls`, `grep`\n * and `cat`. There is no search endpoint and no embedding step, deliberately;\n * see the NO EMBEDDINGS decision in CLAUDE.md.\n *\n * ```ts\n * const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });\n * await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');\n * await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });\n * ```\n */\nexport class KnowledgeBase {\n\t/** Workspace-unique, and what an agent configuration stores. Not editable:\n\t * renaming it would move every attachment silently. */\n\treadonly key: string;\n\n\treadonly #http: Http;\n\tprivate snapshot?: KnowledgeBaseData;\n\n\tconstructor(http: Http, key: string, snapshot?: KnowledgeBaseData) {\n\t\tthis.#http = http;\n\t\tthis.key = key;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/knowledge/bases/${encodeURIComponent(this.key)}`;\n\t}\n\n\t/** The base's current record: name, description, file count, timestamps. */\n\tasync get(): Promise<KnowledgeBaseData> {\n\t\tconst res = await this.#http.get<{ base: KnowledgeBaseData }>(this.base);\n\t\tthis.snapshot = res.base;\n\t\treturn res.base;\n\t}\n\n\t/** What was known at the time this handle was made, without a round trip.\n\t * Undefined on a handle built from a bare key. */\n\tget data(): KnowledgeBaseData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** Change the display name or description. The KEY is deliberately not\n\t * changeable here: agent configurations point at it. */\n\tasync update(fields: { name?: string; description?: string }): Promise<KnowledgeBase> {\n\t\tconst res = await this.#http.patch<{ base: KnowledgeBaseData }>(this.base, fields);\n\t\tthis.snapshot = res.base;\n\t\treturn new KnowledgeBase(this.#http, this.key, res.base);\n\t}\n\n\t/**\n\t * Delete the base and everything in it.\n\t *\n\t * REFUSES while any agent still attaches it, with an error naming them. That\n\t * refusal is what makes attaching safe: an agent configuration that named a\n\t * base at save time would otherwise find it missing the next morning. Call\n\t * `attachments()` first if you want to know before you try.\n\t */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/**\n\t * Which agents read this base.\n\t *\n\t * The question worth asking before deleting or reorganising one: who breaks?\n\t * Read from each agent's current configuration, so it reflects what a session\n\t * would actually mount rather than what somebody typed into a draft.\n\t */\n\tasync attachments(): Promise<{ agentId: string; agentKey?: string; name?: string }[]> {\n\t\tconst res = await this.#http.get<{\n\t\t\tagents: { agentId: string; agentKey?: string; name?: string }[];\n\t\t}>(`${this.base}/attachments`);\n\t\treturn res.agents ?? [];\n\t}\n\n\t/**\n\t * The files in this base.\n\t *\n\t * Paths, not flat names, so `/pricing/tiers.md` is a file inside `pricing/`\n\t * and the session mount reproduces that shape exactly.\n\t */\n\treadonly files = {\n\t\t/**\n\t\t * Upload, or REPLACE what is already at this path. A path holds one file;\n\t\t * re-uploading it is a correction, not a second copy.\n\t\t */\n\t\tput: async (\n\t\t\tpath: string,\n\t\t\tbytes: Uint8Array | ArrayBuffer,\n\t\t\tcontentType?: string,\n\t\t): Promise<KnowledgeFileEntry> => {\n\t\t\tconst res = await this.#http.request<{ file: KnowledgeFileEntry }>(`${this.base}/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t});\n\t\t\treturn res.file;\n\t\t},\n\n\t\t/** List files, optionally under one prefix. Ordered by path, so a listing\n\t\t * reads like a directory rather than an upload log. */\n\t\tlist: async (prefix = '/'): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst res = await this.#http.get<{ files: KnowledgeFileEntry[] }>(`${this.base}/files`, {\n\t\t\t\t...(prefix && prefix !== '/' ? { prefix } : {}),\n\t\t\t});\n\t\t\treturn res.files ?? [];\n\t\t},\n\n\t\t/**\n\t\t * Read a file's bytes back.\n\t\t *\n\t\t * Exists because until now you could write a file and list it but never\n\t\t * read it, so a script had no way to verify its own upload except by\n\t\t * asking an agent.\n\t\t */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\t// `send`, not `request`. `request` ALWAYS parses the body as JSON; the\n\t\t\t// `raw` flag it used to be passed was declared in the options type and\n\t\t\t// read by nothing, so this returned a parsed object (or threw \"Expected\n\t\t\t// JSON\") and then called `.arrayBuffer()` on it. Every call failed.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\n\t\t/**\n\t\t * Read a file back as a string.\n\t\t *\n\t\t * `get` returns bytes, which is right for a PDF and awkward for text: you\n\t\t * decode, change something, and encode again. This is the same file\n\t\t * already decoded, for the case people actually hit, which is reading a\n\t\t * document to check it or to correct it.\n\t\t */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\t// Same fix as `get` above, and this one failed in a nastier way: for a\n\t\t\t// JSON file `request` PARSED it and returned an object, so the failure\n\t\t\t// was `.text is not a function`; for markdown it threw \"Expected JSON\n\t\t\t// from ...\". Two different errors, one cause, neither mentioning it.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn res.text();\n\t\t},\n\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Prepare a file again.\n\t\t *\n\t\t * Preparation happens by itself when a file is uploaded, so this is for\n\t\t * the times it did not work: a scan that came out blank, or a document\n\t\t * that failed before we could read its format and can now.\n\t\t *\n\t\t * It also hands back a file you have edited by hand. Anything you write\n\t\t * yourself is left alone by later runs, and calling this returns it to\n\t\t * being generated.\n\t\t *\n\t\t * Returns immediately. Watch `status` on the file listing to see it\n\t\t * finish.\n\t\t */\n\t\tretry: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/files/retry`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Upload a whole directory, preserving its structure under `prefix`.\n\t\t *\n\t\t * Sugar over repeated `put`, but it is the operation people actually\n\t\t * perform, and hand-rolling the walk is where mistakes about path prefixes\n\t\t * get made. Node and Bun only: it reads the local filesystem.\n\t\t *\n\t\t * Sequential on purpose. A knowledge base is uploaded once in a while, not\n\t\t * in a hot path, and a burst of parallel writes against one base is a good\n\t\t * way to meet a rate limit for no gain.\n\t\t */\n\t\tputDirectory: async (\n\t\t\tdir: string,\n\t\t\toptions: { prefix?: string; include?: string[] } = {},\n\t\t): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst { readdir, readFile } = await import('node:fs/promises');\n\t\t\tconst { join, relative, posix, sep } = await import('node:path');\n\n\t\t\tconst prefix = (options.prefix ?? '/').replace(/\\/+$/, '');\n\t\t\tconst matches = (rel: string): boolean => {\n\t\t\t\tif (!options.include || options.include.length === 0) return true;\n\t\t\t\treturn options.include.some((pattern) => globMatches(pattern, rel));\n\t\t\t};\n\n\t\t\tconst walk = async (current: string): Promise<string[]> => {\n\t\t\t\tconst entries = await readdir(current, { withFileTypes: true });\n\t\t\t\tconst out: string[] = [];\n\t\t\t\tfor (const e of entries) {\n\t\t\t\t\tconst full = join(current, e.name);\n\t\t\t\t\tif (e.isDirectory()) out.push(...(await walk(full)));\n\t\t\t\t\telse if (e.isFile()) out.push(full);\n\t\t\t\t}\n\t\t\t\treturn out;\n\t\t\t};\n\n\t\t\tconst uploaded: KnowledgeFileEntry[] = [];\n\t\t\tfor (const full of await walk(dir)) {\n\t\t\t\tconst rel = relative(dir, full).split(sep).join(posix.sep);\n\t\t\t\tif (!matches(rel)) continue;\n\t\t\t\tconst bytes = await readFile(full);\n\t\t\t\tuploaded.push(\n\t\t\t\t\tawait this.files.put(\n\t\t\t\t\t\t`${prefix}/${rel}`,\n\t\t\t\t\t\tnew Uint8Array(bytes),\n\t\t\t\t\t\tcontentTypeFor(rel),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn uploaded;\n\t\t},\n\t};\n\n\t/**\n\t * The agent that looks after this base.\n\t *\n\t * Every base has one. It reads the documents you upload, turns the ones an\n\t * agent cannot open into text, and keeps a list of what is in here. That\n\t * happens by itself; these methods are for asking it to do something\n\t * specific.\n\t *\n\t * It lives in your own workspace, so its runs cost turns like any other\n\t * agent's, and you can see what it did.\n\t */\n\treadonly librarian = {\n\t\t/**\n\t\t * Work through the whole base: read what has not been read, and rewrite\n\t\t * the list of what is in here.\n\t\t *\n\t\t * The same routine every time, which is the difference between this and\n\t\t * `ask`. Reach for it after uploading a lot at once, or when something\n\t\t * looks out of date.\n\t\t *\n\t\t * RETURNS IMMEDIATELY. The work is queued and runs within about a minute,\n\t\t * because reading a base can take several minutes and no HTTP call should\n\t\t * be held open that long. Watch `status` on the file listing to see it\n\t\t * finish, exactly as with `files.retry`.\n\t\t */\n\t\torganize: async (): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/librarian/organize`, {});\n\t\t},\n\n\t\t/**\n\t\t * Ask the librarian to do something to this base.\n\t\t *\n\t\t * 'split the handbook by chapter'\n\t\t * 'the text from that scan came out garbled, read it again'\n\t\t * 'what is in here about pricing?'\n\t\t *\n\t\t * It can add and change files, which is its job. Your documents are not\n\t\t * sacred to it, so ask it for what you want rather than working around it.\n\t\t */\n\t\task: async (text: string): Promise<{ reply: string }> => {\n\t\t\treturn this.#http.post<{ reply: string }>(`${this.base}/librarian/ask`, { text });\n\t\t},\n\t};\n}\n\n/**\n * Enough glob for `**\\/*.md` and `docs/*.txt`, which is what `include` is for.\n *\n * Deliberately not a glob library. Pulling a dependency into a published SDK so\n * that one convenience method can match filenames is a poor trade, and the\n * patterns people pass here are simple. Anything more complex: list the files\n * yourself and call `put`.\n */\nfunction globMatches(pattern: string, path: string): boolean {\n\tconst rx = pattern\n\t\t.split('/')\n\t\t.map((seg) => {\n\t\t\tif (seg === '**') return '.*';\n\t\t\treturn seg.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*');\n\t\t})\n\t\t.join('/')\n\t\t// `**/` should also match zero directories, so `**\\/*.md` finds `a.md`.\n\t\t.replace(/\\.\\*\\//g, '(?:.*/)?');\n\treturn new RegExp(`^${rx}$`).test(path);\n}\n\n/** A content type from the extension, so uploads are not all octet-stream. */\nfunction contentTypeFor(path: string): string {\n\tconst ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase();\n\tconst known: Record<string, string> = {\n\t\tmd: 'text/markdown',\n\t\tmarkdown: 'text/markdown',\n\t\ttxt: 'text/plain',\n\t\tcsv: 'text/csv',\n\t\tjson: 'application/json',\n\t\thtml: 'text/html',\n\t\tpdf: 'application/pdf',\n\t};\n\treturn known[ext] ?? 'application/octet-stream';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"knowledge-base.js","sourceRoot":"","sources":["../src/knowledge-base.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Http } from './http.ts';\nimport type { KnowledgeBaseData, KnowledgeFileEntry } from './types.ts';\n\n/**\n * A knowledge base: a named folder of files your agents read.\n *\n * A base lives on the WORKSPACE, not on one agent, and an agent configuration\n * attaches it by key. Two agents attaching the same base is the point: correct a\n * file once and both see the correction.\n *\n * Attached bases are mounted READ-ONLY at `/knowledge/<key>/` when a session\n * starts, so the agent finds things the way a person would, with `ls`, `grep`\n * and `cat`. There is no search endpoint and no embedding step, deliberately;\n * see the NO EMBEDDINGS decision in CLAUDE.md.\n *\n * ```ts\n * const kb = await ws.knowledgeBases.create({ key: 'product-docs', name: 'Docs' });\n * await kb.files.put('/pricing/tiers.md', bytes, 'text/markdown');\n * await ws.agents.create({ name: 'Nava', knowledge: ['product-docs'] });\n * ```\n */\n/** A file's path as the API stores it: always with one leading slash. */\nfunction storedPath(path: string): string {\n\treturn `/${path.replace(/^\\/+/, '')}`;\n}\n\nexport class KnowledgeBase {\n\t/** Workspace-unique, and what an agent configuration stores. Not editable:\n\t * renaming it would move every attachment silently. */\n\treadonly key: string;\n\n\treadonly #http: Http;\n\tprivate snapshot?: KnowledgeBaseData;\n\n\tconstructor(http: Http, key: string, snapshot?: KnowledgeBaseData) {\n\t\tthis.#http = http;\n\t\tthis.key = key;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\tprivate get base(): string {\n\t\treturn `/v1/knowledge/bases/${encodeURIComponent(this.key)}`;\n\t}\n\n\t/** The base's current record: name, description, file count, timestamps. */\n\tasync get(): Promise<KnowledgeBaseData> {\n\t\tconst res = await this.#http.get<{ base: KnowledgeBaseData }>(this.base);\n\t\tthis.snapshot = res.base;\n\t\treturn res.base;\n\t}\n\n\t/** What was known at the time this handle was made, without a round trip.\n\t * Undefined on a handle built from a bare key. */\n\tget data(): KnowledgeBaseData | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** Change the display name or description. The KEY is deliberately not\n\t * changeable here: agent configurations point at it. */\n\tasync update(fields: { name?: string; description?: string }): Promise<KnowledgeBase> {\n\t\tconst res = await this.#http.patch<{ base: KnowledgeBaseData }>(this.base, fields);\n\t\tthis.snapshot = res.base;\n\t\treturn new KnowledgeBase(this.#http, this.key, res.base);\n\t}\n\n\t/**\n\t * Delete the base and everything in it.\n\t *\n\t * REFUSES while any agent still attaches it, with an error naming them. That\n\t * refusal is what makes attaching safe: an agent configuration that named a\n\t * base at save time would otherwise find it missing the next morning. Call\n\t * `attachments()` first if you want to know before you try.\n\t */\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(this.base);\n\t}\n\n\t/**\n\t * Which agents read this base.\n\t *\n\t * The question worth asking before deleting or reorganising one: who breaks?\n\t * Read from each agent's current configuration, so it reflects what a session\n\t * would actually mount rather than what somebody typed into a draft.\n\t */\n\tasync attachments(): Promise<{ agentId: string; agentKey?: string; name?: string }[]> {\n\t\tconst res = await this.#http.get<{\n\t\t\tagents: { agentId: string; agentKey?: string; name?: string }[];\n\t\t}>(`${this.base}/attachments`);\n\t\treturn res.agents ?? [];\n\t}\n\n\t/**\n\t * The files in this base.\n\t *\n\t * Paths, not flat names, so `/pricing/tiers.md` is a file inside `pricing/`\n\t * and the session mount reproduces that shape exactly.\n\t */\n\treadonly files = {\n\t\t/**\n\t\t * Upload, or REPLACE what is already at this path. A path holds one file;\n\t\t * re-uploading it is a correction, not a second copy.\n\t\t */\n\t\tput: async (\n\t\t\tpath: string,\n\t\t\tbytes: Uint8Array | ArrayBuffer,\n\t\t\tcontentType?: string,\n\t\t): Promise<KnowledgeFileEntry> => {\n\t\t\tconst res = await this.#http.request<{ file: KnowledgeFileEntry }>(`${this.base}/files`, {\n\t\t\t\tmethod: 'PUT',\n\t\t\t\tbytes,\n\t\t\t\tquery: { path },\n\t\t\t\t...(contentType && { contentType }),\n\t\t\t});\n\t\t\treturn res.file;\n\t\t},\n\n\t\t/** List files, optionally under one prefix. Ordered by path, so a listing\n\t\t * reads like a directory rather than an upload log. */\n\t\tlist: async (prefix = '/'): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst res = await this.#http.get<{ files: KnowledgeFileEntry[] }>(`${this.base}/files`, {\n\t\t\t\t...(prefix && prefix !== '/' ? { prefix } : {}),\n\t\t\t});\n\t\t\treturn res.files ?? [];\n\t\t},\n\n\t\t/**\n\t\t * Read a file's bytes back.\n\t\t *\n\t\t * Exists because until now you could write a file and list it but never\n\t\t * read it, so a script had no way to verify its own upload except by\n\t\t * asking an agent.\n\t\t */\n\t\tget: async (path: string): Promise<Uint8Array> => {\n\t\t\t// `send`, not `request`. `request` ALWAYS parses the body as JSON; the\n\t\t\t// `raw` flag it used to be passed was declared in the options type and\n\t\t\t// read by nothing, so this returned a parsed object (or threw \"Expected\n\t\t\t// JSON\") and then called `.arrayBuffer()` on it. Every call failed.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn new Uint8Array(await res.arrayBuffer());\n\t\t},\n\n\t\t/**\n\t\t * Read a file back as a string.\n\t\t *\n\t\t * `get` returns bytes, which is right for a PDF and awkward for text: you\n\t\t * decode, change something, and encode again. This is the same file\n\t\t * already decoded, for the case people actually hit, which is reading a\n\t\t * document to check it or to correct it.\n\t\t */\n\t\ttext: async (path: string): Promise<string> => {\n\t\t\t// Same fix as `get` above, and this one failed in a nastier way: for a\n\t\t\t// JSON file `request` PARSED it and returned an object, so the failure\n\t\t\t// was `.text is not a function`; for markdown it threw \"Expected JSON\n\t\t\t// from ...\". Two different errors, one cause, neither mentioning it.\n\t\t\tconst res = await this.#http.send(`${this.base}/files/content`, { query: { path } });\n\t\t\treturn res.text();\n\t\t},\n\n\t\tremove: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.delete(`${this.base}/files`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Prepare a file again.\n\t\t *\n\t\t * Preparation happens by itself when a file is uploaded, so this is for\n\t\t * the times it did not work: a scan that came out blank, or a document\n\t\t * that failed before we could read its format and can now.\n\t\t *\n\t\t * It also hands back a file you have edited by hand. Anything you write\n\t\t * yourself is left alone by later runs, and calling this returns it to\n\t\t * being generated.\n\t\t *\n\t\t * Returns immediately. Watch `status` on the file listing to see it\n\t\t * finish.\n\t\t */\n\t\tretry: async (path: string): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/files/retry`, { path });\n\t\t},\n\n\t\t/**\n\t\t * Make the base hold exactly these files: write each one, then remove\n\t\t * every file not among them.\n\t\t *\n\t\t * For an app that keeps a base in step with a source it reads again (a\n\t\t * website, a store): a page the source dropped leaves the base too.\n\t\t * Paths are compared the way the API stores them, with a leading slash,\n\t\t * so `pages/home.md` and `/pages/home.md` are the same file. Comparing\n\t\t * them as written once removed every file a sync had just written.\n\t\t */\n\t\tmirror: async (\n\t\t\tfiles: { path: string; text: string; contentType?: string }[],\n\t\t): Promise<{ written: number; removed: number }> => {\n\t\t\tconst wanted = new Set(files.map((f) => storedPath(f.path)));\n\t\t\tfor (const file of files) {\n\t\t\t\tawait this.files.put(file.path, new TextEncoder().encode(file.text), file.contentType ?? 'text/markdown');\n\t\t\t}\n\t\t\tlet removed = 0;\n\t\t\tfor (const entry of await this.files.list()) {\n\t\t\t\tif (wanted.has(storedPath(entry.path))) continue;\n\t\t\t\tawait this.files.remove(entry.path);\n\t\t\t\tremoved++;\n\t\t\t}\n\t\t\treturn { written: files.length, removed };\n\t\t},\n\n\t\t/**\n\t\t * Upload a whole directory, preserving its structure under `prefix`.\n\t\t *\n\t\t * Sugar over repeated `put`, but it is the operation people actually\n\t\t * perform, and hand-rolling the walk is where mistakes about path prefixes\n\t\t * get made. Node and Bun only: it reads the local filesystem.\n\t\t *\n\t\t * Sequential on purpose. A knowledge base is uploaded once in a while, not\n\t\t * in a hot path, and a burst of parallel writes against one base is a good\n\t\t * way to meet a rate limit for no gain.\n\t\t */\n\t\tputDirectory: async (\n\t\t\tdir: string,\n\t\t\toptions: { prefix?: string; include?: string[] } = {},\n\t\t): Promise<KnowledgeFileEntry[]> => {\n\t\t\tconst { readdir, readFile } = await import('node:fs/promises');\n\t\t\tconst { join, relative, posix, sep } = await import('node:path');\n\n\t\t\tconst prefix = (options.prefix ?? '/').replace(/\\/+$/, '');\n\t\t\tconst matches = (rel: string): boolean => {\n\t\t\t\tif (!options.include || options.include.length === 0) return true;\n\t\t\t\treturn options.include.some((pattern) => globMatches(pattern, rel));\n\t\t\t};\n\n\t\t\tconst walk = async (current: string): Promise<string[]> => {\n\t\t\t\tconst entries = await readdir(current, { withFileTypes: true });\n\t\t\t\tconst out: string[] = [];\n\t\t\t\tfor (const e of entries) {\n\t\t\t\t\tconst full = join(current, e.name);\n\t\t\t\t\tif (e.isDirectory()) out.push(...(await walk(full)));\n\t\t\t\t\telse if (e.isFile()) out.push(full);\n\t\t\t\t}\n\t\t\t\treturn out;\n\t\t\t};\n\n\t\t\tconst uploaded: KnowledgeFileEntry[] = [];\n\t\t\tfor (const full of await walk(dir)) {\n\t\t\t\tconst rel = relative(dir, full).split(sep).join(posix.sep);\n\t\t\t\tif (!matches(rel)) continue;\n\t\t\t\tconst bytes = await readFile(full);\n\t\t\t\tuploaded.push(\n\t\t\t\t\tawait this.files.put(\n\t\t\t\t\t\t`${prefix}/${rel}`,\n\t\t\t\t\t\tnew Uint8Array(bytes),\n\t\t\t\t\t\tcontentTypeFor(rel),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn uploaded;\n\t\t},\n\t};\n\n\t/**\n\t * The agent that looks after this base.\n\t *\n\t * Every base has one. It reads the documents you upload, turns the ones an\n\t * agent cannot open into text, and keeps a list of what is in here. That\n\t * happens by itself; these methods are for asking it to do something\n\t * specific.\n\t *\n\t * It lives in your own workspace, so its runs cost turns like any other\n\t * agent's, and you can see what it did.\n\t */\n\treadonly librarian = {\n\t\t/**\n\t\t * Work through the whole base: read what has not been read, and rewrite\n\t\t * the list of what is in here.\n\t\t *\n\t\t * The same routine every time, which is the difference between this and\n\t\t * `ask`. Reach for it after uploading a lot at once, or when something\n\t\t * looks out of date.\n\t\t *\n\t\t * RETURNS IMMEDIATELY. The work is queued and runs within about a minute,\n\t\t * because reading a base can take several minutes and no HTTP call should\n\t\t * be held open that long. Watch `status` on the file listing to see it\n\t\t * finish, exactly as with `files.retry`.\n\t\t */\n\t\torganize: async (): Promise<void> => {\n\t\t\tawait this.#http.post(`${this.base}/librarian/organize`, {});\n\t\t},\n\n\t\t/**\n\t\t * Ask the librarian to do something to this base.\n\t\t *\n\t\t * 'split the handbook by chapter'\n\t\t * 'the text from that scan came out garbled, read it again'\n\t\t * 'what is in here about pricing?'\n\t\t *\n\t\t * It can add and change files, which is its job. Your documents are not\n\t\t * sacred to it, so ask it for what you want rather than working around it.\n\t\t */\n\t\task: async (text: string): Promise<{ reply: string }> => {\n\t\t\treturn this.#http.post<{ reply: string }>(`${this.base}/librarian/ask`, { text });\n\t\t},\n\t};\n}\n\n/**\n * Enough glob for `**\\/*.md` and `docs/*.txt`, which is what `include` is for.\n *\n * Deliberately not a glob library. Pulling a dependency into a published SDK so\n * that one convenience method can match filenames is a poor trade, and the\n * patterns people pass here are simple. Anything more complex: list the files\n * yourself and call `put`.\n */\nfunction globMatches(pattern: string, path: string): boolean {\n\tconst rx = pattern\n\t\t.split('/')\n\t\t.map((seg) => {\n\t\t\tif (seg === '**') return '.*';\n\t\t\treturn seg.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*');\n\t\t})\n\t\t.join('/')\n\t\t// `**/` should also match zero directories, so `**\\/*.md` finds `a.md`.\n\t\t.replace(/\\.\\*\\//g, '(?:.*/)?');\n\treturn new RegExp(`^${rx}$`).test(path);\n}\n\n/** A content type from the extension, so uploads are not all octet-stream. */\nfunction contentTypeFor(path: string): string {\n\tconst ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase();\n\tconst known: Record<string, string> = {\n\t\tmd: 'text/markdown',\n\t\tmarkdown: 'text/markdown',\n\t\ttxt: 'text/plain',\n\t\tcsv: 'text/csv',\n\t\tjson: 'application/json',\n\t\thtml: 'text/html',\n\t\tpdf: 'application/pdf',\n\t};\n\treturn known[ext] ?? 'application/octet-stream';\n}\n"]}
|
package/package.json
CHANGED
package/partner-app.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export interface PartnerAppOptions {
|
|
2
|
+
/** The client id AgentNava registered for your app (`pa_...`). */
|
|
3
|
+
clientId: string;
|
|
4
|
+
/** Its secret (`pas_...`), shown once at registration. Needed by exchangeCode and grantCredits. */
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
/** The API. Defaults to `AGENTNAVA_BASE_URL`, then `https://api.agentnava.com`. */
|
|
7
|
+
baseURL?: string;
|
|
8
|
+
/** Where people sign in. Defaults to `AGENTNAVA_APP_URL`, then `https://agentnava.com`. */
|
|
9
|
+
appURL?: string;
|
|
10
|
+
/** Per-request timeout in milliseconds. Default 30_000. */
|
|
11
|
+
timeout?: number;
|
|
12
|
+
/** Swap the fetch implementation, for tests or a proxy. */
|
|
13
|
+
fetch?: typeof globalThis.fetch;
|
|
14
|
+
}
|
|
15
|
+
export interface ExchangedCode {
|
|
16
|
+
/** A workspace API key. It does not expire; the workspace's owner can remove it. */
|
|
17
|
+
apiKey: string;
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
/** What the person allowed, as registered: `agents`, and `credits` for an app that bills. */
|
|
20
|
+
permissions: string[];
|
|
21
|
+
/** The API the key belongs to, the one this exchange used. Pass it to AgentNava.getWorkspace with the key. */
|
|
22
|
+
baseURL: string;
|
|
23
|
+
}
|
|
24
|
+
export interface CreditGrant {
|
|
25
|
+
/** False when this reference was already credited: nothing was added this time. */
|
|
26
|
+
granted: boolean;
|
|
27
|
+
balanceCents: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class PartnerApp {
|
|
30
|
+
#private;
|
|
31
|
+
readonly clientId: string;
|
|
32
|
+
constructor(options: PartnerAppOptions);
|
|
33
|
+
/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */
|
|
34
|
+
pkce(): Promise<{
|
|
35
|
+
verifier: string;
|
|
36
|
+
challenge: string;
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.
|
|
40
|
+
* `installId` names one install of your app (a store, say): signing in again then replaces only that
|
|
41
|
+
* install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.
|
|
42
|
+
* `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is
|
|
43
|
+
* offered for this install ("Copperleaf Pantry (Shopify)"), one per install. Up to 80 characters.
|
|
44
|
+
*/
|
|
45
|
+
authorizeUrl(args: {
|
|
46
|
+
redirectUri: string;
|
|
47
|
+
state: string;
|
|
48
|
+
codeChallenge: string;
|
|
49
|
+
installId?: string;
|
|
50
|
+
workspaceName?: string;
|
|
51
|
+
}): string;
|
|
52
|
+
/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */
|
|
53
|
+
exchangeCode(args: {
|
|
54
|
+
code: string;
|
|
55
|
+
redirectUri: string;
|
|
56
|
+
codeVerifier: string;
|
|
57
|
+
}): Promise<ExchangedCode>;
|
|
58
|
+
/**
|
|
59
|
+
* Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.
|
|
60
|
+
* With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.
|
|
61
|
+
* `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.
|
|
62
|
+
*/
|
|
63
|
+
grantCredits(args: {
|
|
64
|
+
workspaceId: string;
|
|
65
|
+
amountCents: number;
|
|
66
|
+
reference: string;
|
|
67
|
+
note: string;
|
|
68
|
+
billingUrl?: string | null;
|
|
69
|
+
}): Promise<CreditGrant>;
|
|
70
|
+
}
|
package/partner-app.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// Sign in with AgentNava, for a partner app's own server: an app another team
|
|
2
|
+
// builds on AgentNava, which asks a person to let it use one of their
|
|
3
|
+
// workspaces. Standard OAuth 2.0 authorization code with PKCE (S256); what the
|
|
4
|
+
// exchange returns is an ordinary workspace API key, for AgentNava.getWorkspace.
|
|
5
|
+
//
|
|
6
|
+
// const app = AgentNava.partnerApp({ clientId, clientSecret });
|
|
7
|
+
// const { verifier, challenge } = await app.pkce(); // keep the verifier
|
|
8
|
+
// redirect(app.authorizeUrl({ redirectUri, state, codeChallenge: challenge }));
|
|
9
|
+
// // ... the person comes back to redirectUri with ?code&state
|
|
10
|
+
// const { apiKey, baseURL } = await app.exchangeCode({ code, redirectUri, codeVerifier: verifier });
|
|
11
|
+
// const ws = AgentNava.getWorkspace({ apiKey, baseURL });
|
|
12
|
+
//
|
|
13
|
+
// The client secret belongs on a server. Never ship it to a browser.
|
|
14
|
+
import { AgentNavaError } from "./errors.js";
|
|
15
|
+
import { Http } from "./http.js";
|
|
16
|
+
function readEnv(name) {
|
|
17
|
+
try {
|
|
18
|
+
return globalThis.process?.env?.[name];
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function base64Url(bytes) {
|
|
25
|
+
let s = '';
|
|
26
|
+
for (const b of bytes)
|
|
27
|
+
s += String.fromCharCode(b);
|
|
28
|
+
return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
29
|
+
}
|
|
30
|
+
/** Web Crypto: a global everywhere this SDK runs except Node 18, which keeps it in node:crypto. */
|
|
31
|
+
async function webCrypto() {
|
|
32
|
+
if (globalThis.crypto?.subtle)
|
|
33
|
+
return globalThis.crypto;
|
|
34
|
+
const { webcrypto } = await import('node:crypto');
|
|
35
|
+
return webcrypto;
|
|
36
|
+
}
|
|
37
|
+
export class PartnerApp {
|
|
38
|
+
clientId;
|
|
39
|
+
#baseURL;
|
|
40
|
+
#appURL;
|
|
41
|
+
/** Signed in with the app's id and secret (HTTP Basic, RFC 6749 section 2.3.1); null without a secret. */
|
|
42
|
+
#http;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
if (!options?.clientId) {
|
|
45
|
+
throw new AgentNavaError({ message: 'clientId is required', code: 'missing_client_id', status: 0 });
|
|
46
|
+
}
|
|
47
|
+
this.clientId = options.clientId;
|
|
48
|
+
this.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? 'https://api.agentnava.com').replace(/\/+$/, '');
|
|
49
|
+
this.#appURL = (options.appURL ?? readEnv('AGENTNAVA_APP_URL') ?? 'https://agentnava.com').replace(/\/+$/, '');
|
|
50
|
+
const secret = options.clientSecret;
|
|
51
|
+
this.#http = secret
|
|
52
|
+
? new Http({ baseURL: this.#baseURL, timeout: options.timeout ?? 30_000, ...(options.fetch && { fetch: options.fetch }) }, `Basic ${btoa(`${encodeURIComponent(this.clientId)}:${encodeURIComponent(secret)}`)}`)
|
|
53
|
+
: null;
|
|
54
|
+
}
|
|
55
|
+
/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */
|
|
56
|
+
async pkce() {
|
|
57
|
+
const webcrypto = await webCrypto();
|
|
58
|
+
const verifier = base64Url(webcrypto.getRandomValues(new Uint8Array(32)));
|
|
59
|
+
const digest = new Uint8Array(await webcrypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));
|
|
60
|
+
return { verifier, challenge: base64Url(digest) };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.
|
|
64
|
+
* `installId` names one install of your app (a store, say): signing in again then replaces only that
|
|
65
|
+
* install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.
|
|
66
|
+
* `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is
|
|
67
|
+
* offered for this install ("Copperleaf Pantry (Shopify)"), one per install. Up to 80 characters.
|
|
68
|
+
*/
|
|
69
|
+
authorizeUrl(args) {
|
|
70
|
+
const url = new URL(`${this.#appURL}/authorize`);
|
|
71
|
+
url.searchParams.set('response_type', 'code');
|
|
72
|
+
url.searchParams.set('client_id', this.clientId);
|
|
73
|
+
url.searchParams.set('redirect_uri', args.redirectUri);
|
|
74
|
+
url.searchParams.set('state', args.state);
|
|
75
|
+
url.searchParams.set('code_challenge', args.codeChallenge);
|
|
76
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
77
|
+
if (args.installId)
|
|
78
|
+
url.searchParams.set('install_id', args.installId);
|
|
79
|
+
if (args.workspaceName)
|
|
80
|
+
url.searchParams.set('workspace_name', args.workspaceName);
|
|
81
|
+
return url.toString();
|
|
82
|
+
}
|
|
83
|
+
/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */
|
|
84
|
+
async exchangeCode(args) {
|
|
85
|
+
const form = new URLSearchParams({
|
|
86
|
+
grant_type: 'authorization_code',
|
|
87
|
+
code: args.code,
|
|
88
|
+
redirect_uri: args.redirectUri,
|
|
89
|
+
code_verifier: args.codeVerifier,
|
|
90
|
+
});
|
|
91
|
+
const out = await this.#signedIn().request('/v1/oauth/token', {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
bytes: new TextEncoder().encode(form.toString()),
|
|
94
|
+
contentType: 'application/x-www-form-urlencoded',
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
apiKey: out.access_token,
|
|
98
|
+
workspaceId: out.workspace_id,
|
|
99
|
+
permissions: (out.scope ?? '').split(' ').filter(Boolean),
|
|
100
|
+
baseURL: this.#baseURL,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.
|
|
105
|
+
* With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.
|
|
106
|
+
* `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.
|
|
107
|
+
*/
|
|
108
|
+
async grantCredits(args) {
|
|
109
|
+
const out = await this.#signedIn().post('/v1/partner/credits', {
|
|
110
|
+
workspace_id: args.workspaceId,
|
|
111
|
+
amount_cents: args.amountCents,
|
|
112
|
+
reference: args.reference,
|
|
113
|
+
note: args.note,
|
|
114
|
+
...(args.billingUrl !== undefined && { billing_url: args.billingUrl || null }),
|
|
115
|
+
});
|
|
116
|
+
return { granted: out.granted, balanceCents: out.balance_cents };
|
|
117
|
+
}
|
|
118
|
+
#signedIn() {
|
|
119
|
+
if (!this.#http) {
|
|
120
|
+
throw new AgentNavaError({ message: 'clientSecret is required on the server', code: 'missing_client_secret', status: 0 });
|
|
121
|
+
}
|
|
122
|
+
return this.#http;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=partner-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"partner-app.js","sourceRoot":"","sources":["../src/partner-app.ts"],"names":[],"mappings":"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","sourcesContent":["// Sign in with AgentNava, for a partner app's own server: an app another team\n// builds on AgentNava, which asks a person to let it use one of their\n// workspaces. Standard OAuth 2.0 authorization code with PKCE (S256); what the\n// exchange returns is an ordinary workspace API key, for AgentNava.getWorkspace.\n//\n// const app = AgentNava.partnerApp({ clientId, clientSecret });\n// const { verifier, challenge } = await app.pkce(); // keep the verifier\n// redirect(app.authorizeUrl({ redirectUri, state, codeChallenge: challenge }));\n// // ... the person comes back to redirectUri with ?code&state\n// const { apiKey, baseURL } = await app.exchangeCode({ code, redirectUri, codeVerifier: verifier });\n// const ws = AgentNava.getWorkspace({ apiKey, baseURL });\n//\n// The client secret belongs on a server. Never ship it to a browser.\n\nimport { AgentNavaError } from './errors.ts';\nimport { Http } from './http.ts';\n\nexport interface PartnerAppOptions {\n\t/** The client id AgentNava registered for your app (`pa_...`). */\n\tclientId: string;\n\t/** Its secret (`pas_...`), shown once at registration. Needed by exchangeCode and grantCredits. */\n\tclientSecret?: string;\n\t/** The API. Defaults to `AGENTNAVA_BASE_URL`, then `https://api.agentnava.com`. */\n\tbaseURL?: string;\n\t/** Where people sign in. Defaults to `AGENTNAVA_APP_URL`, then `https://agentnava.com`. */\n\tappURL?: string;\n\t/** Per-request timeout in milliseconds. Default 30_000. */\n\ttimeout?: number;\n\t/** Swap the fetch implementation, for tests or a proxy. */\n\tfetch?: typeof globalThis.fetch;\n}\n\nexport interface ExchangedCode {\n\t/** A workspace API key. It does not expire; the workspace's owner can remove it. */\n\tapiKey: string;\n\tworkspaceId: string;\n\t/** What the person allowed, as registered: `agents`, and `credits` for an app that bills. */\n\tpermissions: string[];\n\t/** The API the key belongs to, the one this exchange used. Pass it to AgentNava.getWorkspace with the key. */\n\tbaseURL: string;\n}\n\nexport interface CreditGrant {\n\t/** False when this reference was already credited: nothing was added this time. */\n\tgranted: boolean;\n\tbalanceCents: number;\n}\n\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\treturn (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n\tlet s = '';\n\tfor (const b of bytes) s += String.fromCharCode(b);\n\treturn btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/** Web Crypto: a global everywhere this SDK runs except Node 18, which keeps it in node:crypto. */\nasync function webCrypto(): Promise<Crypto> {\n\tif (globalThis.crypto?.subtle) return globalThis.crypto;\n\tconst { webcrypto } = await import('node:crypto');\n\treturn webcrypto as unknown as Crypto;\n}\n\nexport class PartnerApp {\n\treadonly clientId: string;\n\treadonly #baseURL: string;\n\treadonly #appURL: string;\n\t/** Signed in with the app's id and secret (HTTP Basic, RFC 6749 section 2.3.1); null without a secret. */\n\treadonly #http: Http | null;\n\n\tconstructor(options: PartnerAppOptions) {\n\t\tif (!options?.clientId) {\n\t\t\tthrow new AgentNavaError({ message: 'clientId is required', code: 'missing_client_id', status: 0 });\n\t\t}\n\t\tthis.clientId = options.clientId;\n\t\tthis.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? 'https://api.agentnava.com').replace(/\\/+$/, '');\n\t\tthis.#appURL = (options.appURL ?? readEnv('AGENTNAVA_APP_URL') ?? 'https://agentnava.com').replace(/\\/+$/, '');\n\t\tconst secret = options.clientSecret;\n\t\tthis.#http = secret\n\t\t\t? new Http(\n\t\t\t\t\t{ baseURL: this.#baseURL, timeout: options.timeout ?? 30_000, ...(options.fetch && { fetch: options.fetch }) },\n\t\t\t\t\t`Basic ${btoa(`${encodeURIComponent(this.clientId)}:${encodeURIComponent(secret)}`)}`,\n\t\t\t\t)\n\t\t\t: null;\n\t}\n\n\t/** A fresh PKCE pair. Keep `verifier` for exchangeCode; send `challenge` in the link. */\n\tasync pkce(): Promise<{ verifier: string; challenge: string }> {\n\t\tconst webcrypto = await webCrypto();\n\t\tconst verifier = base64Url(webcrypto.getRandomValues(new Uint8Array(32)));\n\t\tconst digest = new Uint8Array(await webcrypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));\n\t\treturn { verifier, challenge: base64Url(digest) };\n\t}\n\n\t/**\n\t * Where to send the person to sign in and allow your app. `redirectUri` must be one you registered.\n\t * `installId` names one install of your app (a store, say): signing in again then replaces only that\n\t * install's earlier key, so two installs in one workspace each keep theirs. Letters, digits and . _ : - only.\n\t * `workspaceName`, for an app that bills its workspaces: the name of the new workspace the person is\n\t * offered for this install (\"Copperleaf Pantry (Shopify)\"), one per install. Up to 80 characters.\n\t */\n\tauthorizeUrl(args: { redirectUri: string; state: string; codeChallenge: string; installId?: string; workspaceName?: string }): string {\n\t\tconst url = new URL(`${this.#appURL}/authorize`);\n\t\turl.searchParams.set('response_type', 'code');\n\t\turl.searchParams.set('client_id', this.clientId);\n\t\turl.searchParams.set('redirect_uri', args.redirectUri);\n\t\turl.searchParams.set('state', args.state);\n\t\turl.searchParams.set('code_challenge', args.codeChallenge);\n\t\turl.searchParams.set('code_challenge_method', 'S256');\n\t\tif (args.installId) url.searchParams.set('install_id', args.installId);\n\t\tif (args.workspaceName) url.searchParams.set('workspace_name', args.workspaceName);\n\t\treturn url.toString();\n\t}\n\n\t/** Swap the one-time code your redirect received for the workspace's API key. A code works once. */\n\tasync exchangeCode(args: { code: string; redirectUri: string; codeVerifier: string }): Promise<ExchangedCode> {\n\t\tconst form = new URLSearchParams({\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode: args.code,\n\t\t\tredirect_uri: args.redirectUri,\n\t\t\tcode_verifier: args.codeVerifier,\n\t\t});\n\t\tconst out = await this.#signedIn().request<{ access_token: string; workspace_id: string; scope?: string }>('/v1/oauth/token', {\n\t\t\tmethod: 'POST',\n\t\t\tbytes: new TextEncoder().encode(form.toString()),\n\t\t\tcontentType: 'application/x-www-form-urlencoded',\n\t\t});\n\t\treturn {\n\t\t\tapiKey: out.access_token,\n\t\t\tworkspaceId: out.workspace_id,\n\t\t\tpermissions: (out.scope ?? '').split(' ').filter(Boolean),\n\t\t\tbaseURL: this.#baseURL,\n\t\t};\n\t}\n\n\t/**\n\t * Add credit the person paid for through your platform. Once per `reference`: sending it again adds nothing.\n\t * With `billingUrl` (https), your app becomes the workspace's billing source: AgentNava's billing screens name your app and link to this page to manage the plan.\n\t * `billingUrl: null` (or '') clears it, so AgentNava bills the workspace again; leaving it out changes nothing.\n\t */\n\tasync grantCredits(args: { workspaceId: string; amountCents: number; reference: string; note: string; billingUrl?: string | null }): Promise<CreditGrant> {\n\t\tconst out = await this.#signedIn().post<{ granted: boolean; balance_cents: number }>('/v1/partner/credits', {\n\t\t\tworkspace_id: args.workspaceId,\n\t\t\tamount_cents: args.amountCents,\n\t\t\treference: args.reference,\n\t\t\tnote: args.note,\n\t\t\t...(args.billingUrl !== undefined && { billing_url: args.billingUrl || null }),\n\t\t});\n\t\treturn { granted: out.granted, balanceCents: out.balance_cents };\n\t}\n\n\t#signedIn(): Http {\n\t\tif (!this.#http) {\n\t\t\tthrow new AgentNavaError({ message: 'clientSecret is required on the server', code: 'missing_client_secret', status: 0 });\n\t\t}\n\t\treturn this.#http;\n\t}\n}\n"]}
|
package/project.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { Http } from './http.ts';
|
|
|
3
3
|
import { type FileEntry, type ProjectData, type ProjectFields } from './types.ts';
|
|
4
4
|
export interface ProjectStartOptions {
|
|
5
5
|
parameters?: Record<string, string | number>;
|
|
6
|
+
/** This conversation's screen can draw widgets, as for `agent.start`. Off unless set. */
|
|
7
|
+
widgets?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export interface FileUrlOptions {
|
|
8
10
|
download?: boolean;
|
|
@@ -55,7 +57,9 @@ export declare class Project {
|
|
|
55
57
|
* It resolves because `getSession` reads both session tables: an id does
|
|
56
58
|
* not say which one it came from, so the route answers for either.
|
|
57
59
|
*/
|
|
58
|
-
conversation(conversationId: string
|
|
60
|
+
conversation(conversationId: string, options?: {
|
|
61
|
+
widgets?: boolean;
|
|
62
|
+
}): Conversation;
|
|
59
63
|
conversations(): Promise<Conversation[]>;
|
|
60
64
|
/** List one directory of the shared folder. Same `path` parameter as
|
|
61
65
|
* everywhere else on this API. */
|
package/project.js
CHANGED
|
@@ -64,7 +64,7 @@ export class Project {
|
|
|
64
64
|
// A project conversation and an agent conversation differ in what STARTED
|
|
65
65
|
// them, not in what you can do with one, so they share the class. The
|
|
66
66
|
// distinction that matters (which id it carries) is in the data.
|
|
67
|
-
return new Conversation(this.#http, data.id);
|
|
67
|
+
return new Conversation(this.#http, data.id, undefined, { widgets: options.widgets === true });
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
70
|
* Address one of this project's conversations by id. No request.
|
|
@@ -88,8 +88,8 @@ export class Project {
|
|
|
88
88
|
* It resolves because `getSession` reads both session tables: an id does
|
|
89
89
|
* not say which one it came from, so the route answers for either.
|
|
90
90
|
*/
|
|
91
|
-
conversation(conversationId) {
|
|
92
|
-
return new Conversation(this.#http, conversationId);
|
|
91
|
+
conversation(conversationId, options = {}) {
|
|
92
|
+
return new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });
|
|
93
93
|
}
|
|
94
94
|
async conversations() {
|
|
95
95
|
// `sessions`, which is what the route actually answers with. This read
|
package/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAkG,MAAM,YAAY,CAAC;AAWzI;;;;;;;GAOG;AACH,MAAM,OAAO,OAAO;IACV,EAAE,CAAS;IACX,KAAK,CAAO;IACb,QAAQ,CAAe;IAE/B,YAAY,IAAU,EAAE,EAAU,EAAE,QAAsB;QACzD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,IAAY,IAAI;QACf,OAAO,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACtD,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,GAAG;QACR,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,MAAqB;QACjC,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC1C,2EAA2E;QAC3E,2CAA2C;QAC3C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAe;QAC7B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe;QAChC,MAAM,IAAI,GAAG,aAAa,CACzB,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,WAAW,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAC7E,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,OAAO;QACZ,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC/C,CAAC;IAED;kBACc;IACd,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,UAA+B,EAAE;QAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAA0B,GAAG,IAAI,CAAC,IAAI,gBAAgB,EAAE;YACzF,GAAG,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;SAC7D,CAAC,CAAC;QACH,0EAA0E;QAC1E,sEAAsE;QACtE,iEAAiE;QACjE,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,YAAY,CAAC,cAAsB;QAClC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;IACrD,CAAC;IAED,KAAK,CAAC,aAAa;QAClB,uEAAuE;QACvE,4EAA4E;QAC5E,4EAA4E;QAC5E,qCAAqC;QACrC,EAAE;QACF,oEAAoE;QACpE,2EAA2E;QAC3E,wDAAwD;QACxD,EAAE;QACF,sEAAsE;QACtE,0EAA0E;QAC1E,4EAA4E;QAC5E,uEAAuE;QACvE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,gBAAgB,CAC5B,CAAC;QACF,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,8EAA8E;IAE9E;uCACmC;IACnC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG;QACrB,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,GAAG,IAAI,CAAC,IAAI,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACvF,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAgB,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY;QAC1B,oEAAoE;QACpE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,GAAG,IAAI,CAAC,IAAI,gBAAgB,EAC5B,EAAE,IAAI,EAAE,CACR,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,UAA0B,EAAE;QACvD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,GAAG,IAAI,CAAC,IAAI,YAAY,EAAE;YAC5E,IAAI;YACJ,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrE,GAAG,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;SACtD,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,GAAG,CAAC;IAChB,CAAC;CACD;AAuBD,mEAAmE;AACnE,SAAS,SAAS,CAAC,KAAgC;IAClD,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,UAAU,CAAC;IACxC,OAAQ,WAAiC,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAE,KAAe,CAAC,CAAC,CAAC,MAAM,CAAC;AAC7F,CAAC;AAED;;;;sDAIsD;AACtD,MAAM,UAAU,aAAa,CAC5B,IAAoE;IAEpE,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAgB,CAAC;IACnE,MAAM,OAAO,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAiB,CAAC;IAC9E,OAAO;QACN,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE;QACf,IAAI,EAAE,CAAC,EAAE,IAAI,IAAI,EAAE;QACnB,IAAI,EAAE,CAAC,EAAE,IAAI,IAAI,IAAI;QACrB,mBAAmB,EAAE,CAAC,EAAE,oBAAoB,IAAI,IAAI;QACpD,0EAA0E;QAC1E,0EAA0E;QAC1E,yDAAyD;QACzD,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC;QAC1B,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ;QACxD,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,OAAO,EAAE,CAAC,CAAC,QAAQ;YACnB,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;YACxB,OAAO,EAAE,CAAC,CAAC,QAAQ;SACnB,CAAC,CAAC;QACH,SAAS,EAAE,CAAC,EAAE,UAAU,IAAI,EAAE;QAC9B,SAAS,EAAE,CAAC,EAAE,UAAU,IAAI,EAAE;KAC9B,CAAC;AACH,CAAC","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"]}
|
|
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\t/** This conversation's screen can draw widgets, as for `agent.start`. Off unless set. */\n\twidgets?: boolean;\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, undefined, { widgets: options.widgets === true });\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, options: { widgets?: boolean } = {}): Conversation {\n\t\treturn new Conversation(this.#http, conversationId, undefined, { widgets: options.widgets === true });\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"]}
|