@inferencesh/sdk 0.21.0 → 0.22.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/CHANGELOG.md +47 -0
- package/dist/agent/api.d.ts +35 -1
- package/dist/agent/api.js +35 -1
- package/dist/agent/reducer.js +23 -7
- package/dist/agent/types.d.ts +29 -4
- package/dist/api/agents.d.ts +9 -3
- package/dist/api/agents.js +87 -63
- package/dist/api/api-keys.d.ts +2 -5
- package/dist/api/apps.d.ts +9 -2
- package/dist/api/apps.js +9 -1
- package/dist/api/chats.d.ts +13 -3
- package/dist/api/chats.js +13 -2
- package/dist/api/engines.d.ts +4 -2
- package/dist/api/engines.js +3 -2
- package/dist/api/flow-runs.d.ts +7 -2
- package/dist/api/flow-runs.js +7 -2
- package/dist/api/flows.d.ts +13 -2
- package/dist/api/flows.js +15 -1
- package/dist/api/knowledge.d.ts +7 -4
- package/dist/api/knowledge.js +6 -4
- package/dist/api/mcp-servers.d.ts +2 -1
- package/dist/api/projects.d.ts +5 -3
- package/dist/api/projects.js +3 -2
- package/dist/api/secrets.d.ts +2 -2
- package/dist/api/tasks.js +38 -56
- package/dist/api/teams.d.ts +4 -2
- package/dist/api/teams.js +3 -2
- package/dist/api/write-bodies.d.ts +110 -0
- package/dist/api/write-bodies.js +1 -0
- package/dist/http/client.d.ts +1 -1
- package/dist/http/client.js +2 -1
- package/dist/http/loopback.d.ts +8 -0
- package/dist/http/loopback.js +18 -0
- package/dist/http/stream-until-terminal.d.ts +55 -0
- package/dist/http/stream-until-terminal.js +88 -0
- package/dist/http/stream.d.ts +1 -0
- package/dist/http/stream.js +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/proxy/endpoints.json +156 -0
- package/dist/proxy/express.d.ts +5 -6
- package/dist/proxy/express.js +18 -2
- package/dist/proxy/hono.d.ts +5 -3
- package/dist/proxy/hono.js +4 -3
- package/dist/proxy/index.d.ts +165 -6
- package/dist/proxy/index.js +409 -25
- package/dist/proxy/nextjs.d.ts +29 -17
- package/dist/proxy/nextjs.js +72 -62
- package/dist/proxy/remix.d.ts +5 -3
- package/dist/proxy/remix.js +4 -3
- package/dist/proxy/svelte.d.ts +5 -4
- package/dist/proxy/svelte.js +4 -4
- package/dist/types.d.ts +423 -6
- package/dist/types.js +111 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.22.0] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
Breaking: the server proxy forwards only app runs and agent chats and refuses every other API call with 403 (see Security); `update()` on projects, knowledge, skills, chats, engines and teams sends PATCH and needs an API that answers PATCH (see Changed); the Next.js proxy handler has no `PUT`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Server proxy options (`@inferencesh/sdk/proxy` and every adapter): `allowedEndpoints` (`namespace/name` globs, e.g. `"ana/helper"`, `"ana/*"`) limits which apps and agents visitors may run; `isAuthenticated(request)` decides per request whether the API key may be used (401 otherwise) and receives the framework's request; `allowUnauthorizedRequests` (default `true`) set to `false` refuses every request unless `isAuthenticated` is given. `@inferencesh/sdk/proxy/nextjs` gains `createHandler(options)` (App Router) and `createPageHandler(options)` (Page Router); `handlers` and `pageHandler` are those with the default options. `PROXY_ENDPOINTS` lists what the proxy forwards. `apiUrl` (default `https://api.inference.sh`) names the API the frontend's client calls; `maxUploadBytes` (default 100 MiB) caps a file uploaded through the proxy.
|
|
17
|
+
- Server proxy: `endpoints` forwards a subset of `PROXY_ENDPOINTS`, built with `pickProxyEndpoint("POST /chats/{id}/messages", { onlyFields, validate })`, which can narrow an entry (fewer body fields, an extra body check) but never widen it; `matchProxyEndpoint(method, path)` finds the entry a request takes. `exactRefs: true` matches `allowedEndpoints` entries against the whole ref, version included ("ana/helper@v2" lets only that version through). `isLoopbackHost` / `isHttpsOrLoopback` export the proxy's https-or-loopback rule.
|
|
18
|
+
- Server proxy: `PROXY_ENDPOINTS` is built from the API's proxy manifest (`src/proxy/endpoints.json`, generated by go/api from its route table for the run-only preset and copied by its `make types`), exported as `PROXY_MANIFEST`. Each entry gains `route` ("POST /chats/{id}/messages") and `scopes` (what the site's key must hold for it); `PROXY_ENDPOINT_RULES` holds the proxy's own body checks by route. `pickProxyEndpoint` takes the manifest route, any placeholder names.
|
|
19
|
+
- `AppsAPI.patch(appId, AppPatchBody)`: PATCH /apps/{id}, changes the app's copy fields sent and keeps the others. `update()` stays POST, since it also edits the version in place.
|
|
20
|
+
- `ChatsAPI.updateSettings()` (POST /chats/{id}/settings: name, visibility, tool approval, hooks, memory), `FlowsAPI.patch()` (PATCH /flows/{id}, only the fields sent) and `FlowsAPI.saveViewport()` (POST /flows/{id}/viewport). `HttpClient` takes `'patch'`.
|
|
21
|
+
- Write body types (`AgentUpdateBody`, `AppUpdateBody`, `ChatUpdateBody`, `EngineUpdateBody`, `FlowUpdateBody`, `FlowPatchBody`, `FlowViewportBody`, `ProjectWriteBody`, `KnowledgeUpdateBody`, `TeamUpdateBody`, `MCPServerUpdateBody`) name the fields each route writes. The update methods take them in place of `Partial<DTO>`: a loaded DTO spread into the call still type-checks; an explicit field the route drops does not.
|
|
22
|
+
- `ApiKeysAPI.create()` takes the generated `CreateApiKeyRequest`, so `scope` (personal or workspace key) and `expires_at` type-check. Existing `{ name, scopes }` calls are unchanged.
|
|
23
|
+
- `ProxyAdapter` takes an optional `signal` that the proxy passes to the upstream fetch.
|
|
24
|
+
- Types: `ScopeRefusedMeta` (`required_scope`, the meta of an `insufficient_scope` or `admin_scopes_not_approved` error), `ErrorCodeAdminScopesNotApproved` and the other admin session error codes, `AgentVersionDTO.visitor_reads` (the scope groups a publication of the agent answers), `ScopesResponse.account_scopes` and `login_preset`, `AppSandbox` on `AppVersionDTO`.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- `update()` on `ProjectsAPI`, `KnowledgeAPI`, `SkillsAPI`, `ChatsAPI`, `EnginesAPI` and `TeamsAPI` sends PATCH instead of POST: a field left out of the body keeps its value. With POST the API wrote every field left out as empty. Needs an API that answers PATCH on these routes; an older API answers 405.
|
|
29
|
+
- `@inferencesh/sdk/proxy/nextjs` `createHandler()` (and `handlers`) return `{ GET, POST }`: the `PUT` handler is gone, since the proxy forwards no PUT call and answered every one with 403. Drop `PUT` from `export const { GET, POST, PUT } = ...`; Next.js now answers PUT with 405.
|
|
30
|
+
|
|
31
|
+
- Tool approvals (`approveTool`, `rejectTool`, `alwaysAllowTool`, `resolveInterrupt`) and widening a chat's approval settings document that they need the account holder's own sign-in: with an API key, an app's token or the server proxy the API answers 403 `requires_sign_in` (`ErrorCodeRequiresSignIn`).
|
|
32
|
+
- Types regenerated from the API: admin elevation types (`AdminElevate*`, `AdminElevation*`), `MeResponse.platform_power` and `scopes`, scope metadata (`sign_in_only`, `not_for_apps`, `costs_credits`, `automation`, `public_rows`, `staff_only`), `ScopePreset.default` and `grants`, `ApiKeyDTO.grants`, the approvals, secrets:reveal and admin scopes (`admin:pricing` included), and error codes including `requires_sign_in`, `insufficient_scope`, `invalid_ttl`, `invalid_code`, `already_processed` and `expired`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- `@inferencesh/sdk/proxy/express` cancels the upstream request when the client disconnects. Before, a disconnected chat or task stream kept its upstream connection open, which kept the host process alive after `server.close()`.
|
|
37
|
+
- `tasks.watch().done`, `client.run({ wait: true })` and the agent's stream-until-idle no longer hang when a stream closes cleanly before a terminal status (load balancer idle timeout, deploy): the SDK reads the task or chat once and settles from it, rejecting when it is still not terminal.
|
|
38
|
+
- Agent chat state always holds `content` as an array: a message the API serializes with `content: null`, or a partial update without content, no longer crashes views that read it. A partial update keeps the content the message had.
|
|
39
|
+
- `SDK_VERSION` reads `package.json`, so it cannot drift from the published version.
|
|
40
|
+
|
|
41
|
+
### Deprecated
|
|
42
|
+
|
|
43
|
+
- `FlowRunsAPI.update()`: the API refuses it, because a flow run has no caller-writable fields. Use `FlowRunsAPI.updateVisibility()` to change who can see a run. The method stays for compile compatibility.
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- The server proxy forwards only what a frontend needs to run things, listed in `PROXY_ENDPOINTS`: `POST /apps/run`, `POST /files` (uploads of run inputs), `POST /agents/run`, `POST /chats`, `POST /chats/{id}/messages`, `POST /chats/{id}/agent`, `GET /agents/{namespace}/{name}`, reading, polling, streaming, stopping and cancelling the resulting task or chat, and client tool results (`POST /tools/{id}`). Every other call (secrets, API keys, billing, account, file listings, app/agent/flow management, admin, task and chat listings and feeds) answers 403 before the API key is attached, whatever the options. Before, it forwarded any path on `*.inference.sh` with the site's key. A server that proxied other calls for its frontend must make them from its own backend.
|
|
48
|
+
- Ids in proxied paths must have the shape the API mints (a lowercase 26-character ULID), so a literal route beside an id is never taken for one: `GET /tasks/stream` (the live feed of every task the key can read), `/tasks/featured`, `/tasks/queue-stats`, `/chats/list` and the like answer 403.
|
|
49
|
+
- Tool approvals are not forwarded: approve, reject, always allow, explain, resolving an interrupt, listing a run's interrupts, and chat settings (`allow_all_tools`). The API takes an approval only from the account holder's own sign-in, never an API key, so these could not work through a proxy, and "allow all tools" would have let any visitor holding a chat id approve every call in it. Agents served through a proxy should not use approval-gated tools; use a published agent (embed) for that.
|
|
50
|
+
- A target URL with credentials (`https://user:pass@...`) is refused with 400.
|
|
51
|
+
- The proxy forwards only to the configured API origin: `apiBaseUrl` / `INFERENCE_API_BASE_URL` when set, else `apiUrl` (default `https://api.inference.sh`). Other `*.inference.sh` hosts are refused with 412. `allowedDomains` patterns must match the whole host.
|
|
52
|
+
- `POST /apps/run` through the proxy takes only `app`, `app_id`, `version_id`, `function`, `input`, `stream` and `wait`: a visitor can no longer set `workers`, `infra`, `session`, `session_timeout`, `webhook`, `run_at`, `setup` or `metadata` (403).
|
|
53
|
+
- `POST /files` through the proxy takes one file per request, with no `category`, and a declared `size` up to `maxUploadBytes` (413 above it). Uploads count against the API key owner's storage quota.
|
|
54
|
+
- With `allowedEndpoints` set, a request that names an app or agent must name an allowed one by its `namespace/name` ref: `app_id`/`version_id` runs and ad-hoc `agent_config` runs are refused, and body keys are matched case-insensitively as the API reads them. Requests about an existing task or chat name it by id and are not checked against `allowedEndpoints`: whoever holds a task or chat id can read, stream, cancel or stop it and send messages in the chat. All visitors share the key, so the id is the only boundary between them.
|
|
55
|
+
- The server proxy (`@inferencesh/sdk/proxy`) refuses a target that is not `https:` with 412 before attaching the API key, so a client can no longer make it send `INFERENCE_API_KEY` over plain http. Plain `http:` is allowed only to a loopback host (`localhost`, `*.localhost`, `127.0.0.0/8`, `[::1]`), for a local API in development. A malformed target with `apiBaseUrl` set answers 400 instead of throwing.
|
|
56
|
+
|
|
10
57
|
## [0.21.0] - 2026-10-08
|
|
11
58
|
|
|
12
59
|
Breaking: `AgentClient.http` requires `fetch`, the `AppTag*` constants are removed, and `StreamableManagerOptions` is a type alias. See Changed and Removed.
|
package/dist/agent/api.d.ts
CHANGED
|
@@ -46,7 +46,23 @@ export declare function submitToolResult(client: AgentClient, toolInvocationId:
|
|
|
46
46
|
};
|
|
47
47
|
form_data?: Record<string, unknown>;
|
|
48
48
|
}): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Approve a call awaiting approval; the api then runs it.
|
|
51
|
+
*
|
|
52
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
53
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
54
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
55
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
56
|
+
*/
|
|
49
57
|
export declare function approveTool(client: AgentClient, toolInvocationId: string): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Reject a call awaiting approval, with an optional reason the agent sees.
|
|
60
|
+
*
|
|
61
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
62
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
63
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
64
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
65
|
+
*/
|
|
50
66
|
export declare function rejectTool(client: AgentClient, toolInvocationId: string, reason?: string): Promise<void>;
|
|
51
67
|
/**
|
|
52
68
|
* What "always allow" can save for a call awaiting approval, narrowest first.
|
|
@@ -57,6 +73,11 @@ export declare function getAlwaysAllowOptions(client: AgentClient, chatId: strin
|
|
|
57
73
|
* Save an "always allow" option (a key from getAlwaysAllowOptions) as chat
|
|
58
74
|
* rules and approve the call once. Without an option the api saves its old
|
|
59
75
|
* default: the command exactly for remote_exec, the whole tool otherwise.
|
|
76
|
+
*
|
|
77
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
78
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
79
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
80
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
60
81
|
*/
|
|
61
82
|
export declare function alwaysAllowTool(client: AgentClient, chatId: string, toolInvocationId: string, option?: string): Promise<AlwaysAllowResultDTO>;
|
|
62
83
|
/**
|
|
@@ -65,8 +86,21 @@ export declare function alwaysAllowTool(client: AgentClient, chatId: string, too
|
|
|
65
86
|
* for the call, so asking again is free.
|
|
66
87
|
*/
|
|
67
88
|
export declare function explainTool(client: AgentClient, chatId: string, toolInvocationId: string): Promise<ToolExplanationDTO>;
|
|
68
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* Change the chat's settings. Answers with the settings as they now are.
|
|
91
|
+
* Widening what runs without approval (allow_all_tools) needs approvals:write,
|
|
92
|
+
* held only by the account holder's own sign-in: other credentials are
|
|
93
|
+
* refused with 403 `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
94
|
+
*/
|
|
69
95
|
export declare function updateChatSettings(client: AgentClient, chatId: string, settings: ChatSettingsRequest): Promise<ChatSettingsDTO>;
|
|
96
|
+
/**
|
|
97
|
+
* Allow or deny an interrupt gate on an agent run.
|
|
98
|
+
*
|
|
99
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
100
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
101
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
102
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
103
|
+
*/
|
|
70
104
|
export declare function resolveInterrupt(client: AgentClient, interruptId: string, decision: 'allow' | 'deny'): Promise<InterruptDTO>;
|
|
71
105
|
export declare function listRunInterrupts(client: AgentClient, runId: string): Promise<InterruptDTO[]>;
|
|
72
106
|
export declare function uploadFile(client: AgentClient, file: globalThis.File): Promise<FileRef>;
|
package/dist/agent/api.js
CHANGED
|
@@ -146,9 +146,25 @@ export async function submitToolResult(client, toolInvocationId, resultOrAction)
|
|
|
146
146
|
const data = typeof resultOrAction === 'string' ? { result: resultOrAction } : resultOrAction;
|
|
147
147
|
await client.http.request('post', `/tools/${toolInvocationId}`, { data });
|
|
148
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Approve a call awaiting approval; the api then runs it.
|
|
151
|
+
*
|
|
152
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
153
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
154
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
155
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
156
|
+
*/
|
|
149
157
|
export async function approveTool(client, toolInvocationId) {
|
|
150
158
|
await client.http.request('post', `/tools/${toolInvocationId}/invoke`);
|
|
151
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Reject a call awaiting approval, with an optional reason the agent sees.
|
|
162
|
+
*
|
|
163
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
164
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
165
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
166
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
167
|
+
*/
|
|
152
168
|
export async function rejectTool(client, toolInvocationId, reason) {
|
|
153
169
|
await client.http.request('post', `/tools/${toolInvocationId}/reject`, { data: { reason } });
|
|
154
170
|
}
|
|
@@ -164,6 +180,11 @@ export async function getAlwaysAllowOptions(client, chatId, toolInvocationId) {
|
|
|
164
180
|
* Save an "always allow" option (a key from getAlwaysAllowOptions) as chat
|
|
165
181
|
* rules and approve the call once. Without an option the api saves its old
|
|
166
182
|
* default: the command exactly for remote_exec, the whole tool otherwise.
|
|
183
|
+
*
|
|
184
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
185
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
186
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
187
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
167
188
|
*/
|
|
168
189
|
export async function alwaysAllowTool(client, chatId, toolInvocationId, option) {
|
|
169
190
|
const data = option ? { option } : {};
|
|
@@ -179,7 +200,12 @@ export async function explainTool(client, chatId, toolInvocationId) {
|
|
|
179
200
|
const resp = await client.http.request('post', `/chats/${chatId}/tools/${toolInvocationId}/explain`);
|
|
180
201
|
return resp.data;
|
|
181
202
|
}
|
|
182
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* Change the chat's settings. Answers with the settings as they now are.
|
|
205
|
+
* Widening what runs without approval (allow_all_tools) needs approvals:write,
|
|
206
|
+
* held only by the account holder's own sign-in: other credentials are
|
|
207
|
+
* refused with 403 `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
208
|
+
*/
|
|
183
209
|
export async function updateChatSettings(client, chatId, settings) {
|
|
184
210
|
const resp = await client.http.request('post', `/chats/${chatId}/settings`, { data: settings });
|
|
185
211
|
return resp.data;
|
|
@@ -187,6 +213,14 @@ export async function updateChatSettings(client, chatId, settings) {
|
|
|
187
213
|
// =========================================================================
|
|
188
214
|
// Interrupt operations
|
|
189
215
|
// =========================================================================
|
|
216
|
+
/**
|
|
217
|
+
* Allow or deny an interrupt gate on an agent run.
|
|
218
|
+
*
|
|
219
|
+
* Needs approvals:write, which only the account holder's own sign-in holds
|
|
220
|
+
* (the web app, or a `belt login` session). An API key, an app's OAuth token
|
|
221
|
+
* or the server proxy is refused with 403 `requires_sign_in`
|
|
222
|
+
* (ErrorCodeRequiresSignIn); the error's message says what to do.
|
|
223
|
+
*/
|
|
190
224
|
export async function resolveInterrupt(client, interruptId, decision) {
|
|
191
225
|
const resp = await client.http.request('post', `/interrupts/${interruptId}/resolve`, {
|
|
192
226
|
data: { decision }
|
package/dist/agent/reducer.js
CHANGED
|
@@ -14,6 +14,20 @@ function deriveChatStatus(run) {
|
|
|
14
14
|
return ChatStatusAwaitingInput;
|
|
15
15
|
return ChatStatusIdle;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Every message enters state through here. The API serializes a message with
|
|
19
|
+
* no content as `content: null` (a nil Go slice), and a partial stream update
|
|
20
|
+
* carries the whole DTO with `content: null` when content is not among its
|
|
21
|
+
* fields. State always holds an array, so ChatMessageDTO.content is what its
|
|
22
|
+
* type says for every consumer; a partial update with null content keeps the
|
|
23
|
+
* content the message already had.
|
|
24
|
+
*/
|
|
25
|
+
function normalizeMessage(message, existing) {
|
|
26
|
+
if (message.content != null)
|
|
27
|
+
return message;
|
|
28
|
+
return { ...message, content: existing?.content ?? [] };
|
|
29
|
+
}
|
|
30
|
+
const normalizeMessages = (messages) => messages.map((m) => normalizeMessage(m));
|
|
17
31
|
// =============================================================================
|
|
18
32
|
// Initial State
|
|
19
33
|
// =============================================================================
|
|
@@ -38,7 +52,7 @@ export function chatReducer(state, action) {
|
|
|
38
52
|
if (!chat) {
|
|
39
53
|
return { ...state, chat: null, messages: [], connectionStatus: 'idle', messageCursor: undefined, hasOlderMessages: undefined };
|
|
40
54
|
}
|
|
41
|
-
const messages =
|
|
55
|
+
const messages = normalizeMessages(chat.chat_messages || []).sort((a, b) => a.order - b.order);
|
|
42
56
|
const cursor = chat?._messageCursor;
|
|
43
57
|
const hasOlder = chat?._hasOlderMessages;
|
|
44
58
|
return { ...state, chat, messages, messageCursor: cursor, hasOlderMessages: hasOlder };
|
|
@@ -93,11 +107,11 @@ export function chatReducer(state, action) {
|
|
|
93
107
|
return { ...state, chat: { ...state.chat, active_run: action.payload, status: deriveChatStatus(action.payload) } };
|
|
94
108
|
}
|
|
95
109
|
case 'SET_MESSAGES':
|
|
96
|
-
return { ...state, messages: action.payload };
|
|
110
|
+
return { ...state, messages: normalizeMessages(action.payload) };
|
|
97
111
|
case 'PREPEND_MESSAGES': {
|
|
98
112
|
const { messages: older, cursor, hasMore } = action.payload;
|
|
99
113
|
const existingIds = new Set(state.messages.map(m => m.id));
|
|
100
|
-
const deduped = older.filter(m => !existingIds.has(m.id));
|
|
114
|
+
const deduped = normalizeMessages(older.filter(m => !existingIds.has(m.id)));
|
|
101
115
|
const merged = [...deduped, ...state.messages].sort((a, b) => a.order - b.order);
|
|
102
116
|
return { ...state, messages: merged, messageCursor: cursor, hasOlderMessages: hasMore };
|
|
103
117
|
}
|
|
@@ -106,7 +120,9 @@ export function chatReducer(state, action) {
|
|
|
106
120
|
const existingIndex = state.messages.findIndex((m) => m.id === message.id);
|
|
107
121
|
if (existingIndex !== -1) {
|
|
108
122
|
const existing = state.messages[existingIndex];
|
|
109
|
-
const updated = action.partial
|
|
123
|
+
const updated = action.partial
|
|
124
|
+
? normalizeMessage({ ...existing, ...message }, existing)
|
|
125
|
+
: normalizeMessage(message);
|
|
110
126
|
if (existing === updated)
|
|
111
127
|
return state;
|
|
112
128
|
const newMessages = [...state.messages];
|
|
@@ -115,12 +131,12 @@ export function chatReducer(state, action) {
|
|
|
115
131
|
}
|
|
116
132
|
if (action.partial)
|
|
117
133
|
return state;
|
|
118
|
-
return { ...state, messages: [...state.messages, message].sort((a, b) => a.order - b.order) };
|
|
134
|
+
return { ...state, messages: [...state.messages, normalizeMessage(message)].sort((a, b) => a.order - b.order) };
|
|
119
135
|
}
|
|
120
136
|
case 'ADD_MESSAGE':
|
|
121
137
|
return {
|
|
122
138
|
...state,
|
|
123
|
-
messages: [...state.messages, action.payload].sort((a, b) => a.order - b.order),
|
|
139
|
+
messages: [...state.messages, normalizeMessage(action.payload)].sort((a, b) => a.order - b.order),
|
|
124
140
|
};
|
|
125
141
|
case 'DELTA_TOKEN': {
|
|
126
142
|
const { messageId, output } = action.payload;
|
|
@@ -131,7 +147,7 @@ export function chatReducer(state, action) {
|
|
|
131
147
|
const target = msgs.find(m => m.id === messageId);
|
|
132
148
|
if (!target)
|
|
133
149
|
return state;
|
|
134
|
-
const textBlock = target.content
|
|
150
|
+
const textBlock = target.content.find(c => c.type === 'text');
|
|
135
151
|
const newContent = textBlock
|
|
136
152
|
? target.content.map(c => c.type === 'text' ? { ...c, text: output.response } : c)
|
|
137
153
|
: [{ type: 'text', text: output.response }, ...target.content];
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -132,9 +132,21 @@ export interface AgentChatActions {
|
|
|
132
132
|
submitToolResult: (toolInvocationId: string, result: string) => Promise<void>;
|
|
133
133
|
/** Answer an MCP tool call's input requests (AwaitingInput status with MCPInputState data) */
|
|
134
134
|
submitMCPInput: (toolInvocationId: string, responses: Record<string, ElicitResult>) => Promise<void>;
|
|
135
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Approve a tool (for HIL approval - AwaitingApproval status)
|
|
137
|
+
*
|
|
138
|
+
* Needs the account holder's own sign-in (approvals:write): with an API
|
|
139
|
+
* key, an app's token or the server proxy it rejects with 403
|
|
140
|
+
* `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
141
|
+
*/
|
|
136
142
|
approveTool: (toolInvocationId: string) => Promise<void>;
|
|
137
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Reject a tool (for HIL approval - AwaitingApproval status)
|
|
145
|
+
*
|
|
146
|
+
* Needs the account holder's own sign-in (approvals:write): with an API
|
|
147
|
+
* key, an app's token or the server proxy it rejects with 403
|
|
148
|
+
* `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
149
|
+
*/
|
|
138
150
|
rejectTool: (toolInvocationId: string, reason?: string) => Promise<void>;
|
|
139
151
|
/**
|
|
140
152
|
* What "always allow" can save for a call awaiting approval, narrowest
|
|
@@ -154,15 +166,28 @@ export interface AgentChatActions {
|
|
|
154
166
|
* api saves its default. A string is accepted from older callers (it was the
|
|
155
167
|
* tool name) and means the default. Rejects with a 409 when the option is
|
|
156
168
|
* stale: read the options again.
|
|
169
|
+
*
|
|
170
|
+
* Needs the account holder's own sign-in (approvals:write): with an API
|
|
171
|
+
* key, an app's token or the server proxy it rejects with 403
|
|
172
|
+
* `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
157
173
|
*/
|
|
158
174
|
alwaysAllowTool: (toolInvocationId: string, choice?: AlwaysAllowChoice | string) => Promise<AlwaysAllowResultDTO | undefined>;
|
|
159
|
-
/**
|
|
175
|
+
/**
|
|
176
|
+
* Change this chat's settings, e.g. allow_all_tools (switching it on approves the calls waiting).
|
|
177
|
+
* Widening approvals needs the account holder's own sign-in: other credentials get 403 `requires_sign_in`.
|
|
178
|
+
*/
|
|
160
179
|
updateChatSettings: (settings: ChatSettingsRequest) => Promise<void>;
|
|
161
180
|
/** Hand this chat to another agent (namespace/name); the next message goes to it. Agents our loop runs only. */
|
|
162
181
|
switchAgent: (agentRef: string) => Promise<void>;
|
|
163
182
|
/** Cancel a queued message before the agent processes it */
|
|
164
183
|
cancelMessage: (messageId: string) => Promise<void>;
|
|
165
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Resolve an interrupt gate (allow or deny)
|
|
186
|
+
*
|
|
187
|
+
* Needs the account holder's own sign-in (approvals:write): with an API
|
|
188
|
+
* key, an app's token or the server proxy it rejects with 403
|
|
189
|
+
* `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
190
|
+
*/
|
|
166
191
|
resolveInterrupt: (interruptId: string, decision: 'allow' | 'deny') => Promise<void>;
|
|
167
192
|
/** Load older messages (scroll-up pagination). Returns true if more exist. */
|
|
168
193
|
loadOlderMessages: () => Promise<boolean>;
|
package/dist/api/agents.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
2
|
import type { Response } from '../http/response';
|
|
3
|
+
import type { AgentUpdateBody } from './write-bodies';
|
|
3
4
|
import { FilesAPI } from './files';
|
|
4
5
|
import { ChatDTO, ChatMessageDTO, ChannelContext, LLMDelta, LLMOutput, AgentConfigInput as AgentConfig, AgentDTO, AgentVersionDTO, CreateAgentRequest, FileDTO as File, InterruptDTO, CursorListRequest, CursorListResponse } from '../types';
|
|
5
6
|
/** Internal tool definition returned by getInternalTools */
|
|
@@ -159,9 +160,10 @@ export declare class AgentsAPI {
|
|
|
159
160
|
*/
|
|
160
161
|
createAgent(data: CreateAgentRequest): Promise<Response<AgentDTO>>;
|
|
161
162
|
/**
|
|
162
|
-
* Update an agent template
|
|
163
|
+
* Update an agent template: its visibility and images, and a new version
|
|
164
|
+
* when `version` is set (see AgentUpdateBody).
|
|
163
165
|
*/
|
|
164
|
-
update(agentId: string, data:
|
|
166
|
+
update(agentId: string, data: AgentUpdateBody): Promise<Response<AgentDTO>>;
|
|
165
167
|
/**
|
|
166
168
|
* Delete an agent template
|
|
167
169
|
*/
|
|
@@ -215,7 +217,11 @@ export declare class AgentsAPI {
|
|
|
215
217
|
form_data?: Record<string, unknown>;
|
|
216
218
|
}): Promise<void>;
|
|
217
219
|
/**
|
|
218
|
-
* Resolve an interrupt gate on an agent run
|
|
220
|
+
* Resolve an interrupt gate on an agent run.
|
|
221
|
+
*
|
|
222
|
+
* Needs approvals:write, which only the account holder's own sign-in holds:
|
|
223
|
+
* with an API key, an app's OAuth token or the server proxy it rejects with
|
|
224
|
+
* 403 `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
219
225
|
*/
|
|
220
226
|
resolveInterrupt(interruptId: string, decision: 'allow' | 'deny'): Promise<Response<InterruptDTO>>;
|
|
221
227
|
/**
|
package/dist/api/agents.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { streamUntilTerminal } from '../http/stream-until-terminal';
|
|
2
2
|
import { PollManager } from '../http/poll';
|
|
3
3
|
import { createLLMDeltaAccumulator } from '../delta';
|
|
4
4
|
import { ToolTypeClient, ToolInvocationStatusAwaitingInput, ToolInvocationStatusInProgress, } from '../types';
|
|
@@ -108,6 +108,10 @@ export class Agent {
|
|
|
108
108
|
let waitPromise = null;
|
|
109
109
|
if (this.chatId && shouldWait) {
|
|
110
110
|
waitPromise = waitFn(options);
|
|
111
|
+
// The stream can fail while the POST is in flight, before anything
|
|
112
|
+
// awaits waitPromise; mark the rejection handled so Node does not treat
|
|
113
|
+
// it as unhandled. The await below still sees it.
|
|
114
|
+
waitPromise.catch(() => { });
|
|
111
115
|
}
|
|
112
116
|
// Make the POST request
|
|
113
117
|
let response;
|
|
@@ -145,8 +149,11 @@ export class Agent {
|
|
|
145
149
|
this.disconnect();
|
|
146
150
|
reject(signal.reason);
|
|
147
151
|
};
|
|
148
|
-
if (signal.aborted)
|
|
149
|
-
|
|
152
|
+
if (signal.aborted) {
|
|
153
|
+
onAbort();
|
|
154
|
+
wait.catch(() => { });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
150
157
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
151
158
|
wait.then(resolve, reject).finally(() => signal.removeEventListener('abort', onAbort));
|
|
152
159
|
});
|
|
@@ -203,89 +210,101 @@ export class Agent {
|
|
|
203
210
|
startStreaming(options = {}) {
|
|
204
211
|
if (!this.chatId)
|
|
205
212
|
return;
|
|
206
|
-
|
|
213
|
+
// Fire-and-forget: a stream failure must not become an unhandled rejection.
|
|
214
|
+
this.streamUntilIdle(options, new TurnGate(false)).catch((error) => {
|
|
215
|
+
console.warn('[Agent] Stream error:', error);
|
|
216
|
+
});
|
|
207
217
|
}
|
|
208
218
|
/** Stream events until chat becomes idle */
|
|
209
219
|
streamUntilIdle(options, gate) {
|
|
210
220
|
if (!this.chatId)
|
|
211
221
|
return Promise.resolve();
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.stream = new StreamableManager({
|
|
216
|
-
request: (init) => this.http.fetch(endpoint, init),
|
|
217
|
-
});
|
|
222
|
+
const chatId = this.chatId;
|
|
223
|
+
this.stream?.stop();
|
|
224
|
+
const handle = streamUntilTerminal((settle) => {
|
|
218
225
|
// Last chat/run observation was idle but the gate wasn't settled yet;
|
|
219
226
|
// a terminal message for this turn can settle it.
|
|
220
227
|
let idlePending = false;
|
|
221
228
|
const onIdle = () => {
|
|
222
229
|
if (gate.settled)
|
|
223
|
-
resolve();
|
|
230
|
+
settle.resolve();
|
|
224
231
|
else
|
|
225
232
|
idlePending = true;
|
|
226
233
|
};
|
|
227
|
-
|
|
234
|
+
const observeChat = (chat) => {
|
|
228
235
|
options.onChat?.(chat);
|
|
229
236
|
gate.observeChat(chat);
|
|
230
237
|
if (!isChatBusy(chat))
|
|
231
238
|
onIdle();
|
|
232
239
|
else
|
|
233
240
|
idlePending = false;
|
|
234
|
-
}
|
|
235
|
-
this.stream.addEventListener('agent_runs', (run) => {
|
|
236
|
-
const asChat = { active_run: run };
|
|
237
|
-
options.onChat?.(asChat);
|
|
238
|
-
gate.observeChat(asChat);
|
|
239
|
-
if (!isChatBusy(asChat))
|
|
240
|
-
onIdle();
|
|
241
|
-
else
|
|
242
|
-
idlePending = false;
|
|
243
|
-
});
|
|
241
|
+
};
|
|
244
242
|
// One accumulator per message being streamed, keyed by the message id
|
|
245
243
|
// the delta names, so a tool-call-only turn never shows the previous
|
|
246
244
|
// message's text. Same attribution rule as the React hooks.
|
|
247
245
|
const deltaAccums = new Map();
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
output: accum.toOutput(),
|
|
261
|
-
seq: evt.seq,
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
this.stream.addEventListener('chat_messages', (message) => {
|
|
265
|
-
// A terminal message receives no further deltas.
|
|
266
|
-
if (isMessageTerminal(message.status))
|
|
267
|
-
deltaAccums.delete(message.id);
|
|
268
|
-
gate.observeMessage(message);
|
|
269
|
-
options.onMessage?.(message);
|
|
270
|
-
if (idlePending && gate.settled)
|
|
271
|
-
resolve();
|
|
272
|
-
if (message.tool_invocations && options.onToolCall) {
|
|
273
|
-
for (const inv of message.tool_invocations) {
|
|
274
|
-
if (this.dispatchedToolCalls.has(inv.id))
|
|
275
|
-
continue;
|
|
276
|
-
if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
|
|
277
|
-
this.dispatchedToolCalls.add(inv.id);
|
|
278
|
-
options.onToolCall({
|
|
279
|
-
id: inv.id,
|
|
280
|
-
name: inv.function?.name || '',
|
|
281
|
-
args: inv.function?.arguments || {},
|
|
282
|
-
});
|
|
246
|
+
return {
|
|
247
|
+
request: (init) => this.http.fetch(`/chats/${chatId}/stream`, init),
|
|
248
|
+
events: {
|
|
249
|
+
chats: (chat) => observeChat(chat),
|
|
250
|
+
agent_runs: (run) => observeChat({ active_run: run }),
|
|
251
|
+
delta: (evt) => {
|
|
252
|
+
if (!options.onDelta || !evt?.delta || !evt.resource_id)
|
|
253
|
+
return;
|
|
254
|
+
let accum = deltaAccums.get(evt.resource_id);
|
|
255
|
+
if (!accum) {
|
|
256
|
+
accum = createLLMDeltaAccumulator();
|
|
257
|
+
deltaAccums.set(evt.resource_id, accum);
|
|
283
258
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
259
|
+
accum.apply(evt.delta);
|
|
260
|
+
options.onDelta({
|
|
261
|
+
messageId: evt.resource_id,
|
|
262
|
+
delta: evt.delta,
|
|
263
|
+
output: accum.toOutput(),
|
|
264
|
+
seq: evt.seq,
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
chat_messages: (message) => {
|
|
268
|
+
// A terminal message receives no further deltas.
|
|
269
|
+
if (isMessageTerminal(message.status))
|
|
270
|
+
deltaAccums.delete(message.id);
|
|
271
|
+
gate.observeMessage(message);
|
|
272
|
+
options.onMessage?.(message);
|
|
273
|
+
if (idlePending && gate.settled)
|
|
274
|
+
settle.resolve();
|
|
275
|
+
if (message.tool_invocations && options.onToolCall) {
|
|
276
|
+
for (const inv of message.tool_invocations) {
|
|
277
|
+
if (this.dispatchedToolCalls.has(inv.id))
|
|
278
|
+
continue;
|
|
279
|
+
if (inv.type === ToolTypeClient && (inv.status === ToolInvocationStatusInProgress || inv.status === ToolInvocationStatusAwaitingInput)) {
|
|
280
|
+
this.dispatchedToolCalls.add(inv.id);
|
|
281
|
+
options.onToolCall({
|
|
282
|
+
id: inv.id,
|
|
283
|
+
name: inv.function?.name || '',
|
|
284
|
+
args: inv.function?.arguments || {},
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
// The stream closed before the chat was seen idle: the chat itself
|
|
292
|
+
// says whether the turn is over.
|
|
293
|
+
reconcile: async () => {
|
|
294
|
+
const { data: chat } = await this.http.request('get', `/chats/${chatId}`);
|
|
295
|
+
observeChat(chat);
|
|
296
|
+
},
|
|
297
|
+
closedMessage: 'Chat stream closed before the chat became idle',
|
|
298
|
+
// Stopped by disconnect()/abort or replaced by a newer stream: the
|
|
299
|
+
// caller stopped waiting on purpose.
|
|
300
|
+
onStopped: () => settle.resolve(),
|
|
301
|
+
// Once the turn is over the stream stays attached, so onChat/onMessage
|
|
302
|
+
// keep reporting until the next turn or disconnect() replaces it.
|
|
303
|
+
keepOpen: true,
|
|
304
|
+
};
|
|
288
305
|
});
|
|
306
|
+
this.stream = handle;
|
|
307
|
+
return handle.done;
|
|
289
308
|
}
|
|
290
309
|
/** Poll until chat becomes idle, dispatching callbacks on changes */
|
|
291
310
|
pollUntilIdle(options, gate) {
|
|
@@ -394,7 +413,8 @@ export class AgentsAPI {
|
|
|
394
413
|
return this.http.request('post', '/agents', { data });
|
|
395
414
|
}
|
|
396
415
|
/**
|
|
397
|
-
* Update an agent template
|
|
416
|
+
* Update an agent template: its visibility and images, and a new version
|
|
417
|
+
* when `version` is set (see AgentUpdateBody).
|
|
398
418
|
*/
|
|
399
419
|
async update(agentId, data) {
|
|
400
420
|
return this.http.request('post', `/agents/${agentId}`, { data });
|
|
@@ -472,7 +492,11 @@ export class AgentsAPI {
|
|
|
472
492
|
await this.http.request('post', `/tools/${toolInvocationId}`, { data: { result } });
|
|
473
493
|
}
|
|
474
494
|
/**
|
|
475
|
-
* Resolve an interrupt gate on an agent run
|
|
495
|
+
* Resolve an interrupt gate on an agent run.
|
|
496
|
+
*
|
|
497
|
+
* Needs approvals:write, which only the account holder's own sign-in holds:
|
|
498
|
+
* with an API key, an app's OAuth token or the server proxy it rejects with
|
|
499
|
+
* 403 `requires_sign_in` (ErrorCodeRequiresSignIn).
|
|
476
500
|
*/
|
|
477
501
|
async resolveInterrupt(interruptId, decision) {
|
|
478
502
|
return this.http.request('post', `/interrupts/${interruptId}/resolve`, {
|
package/dist/api/api-keys.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
2
|
import type { Response } from '../http/response';
|
|
3
|
-
import { ApiKeyDTO, CursorListRequest, CursorListResponse } from '../types';
|
|
3
|
+
import { ApiKeyDTO, CreateApiKeyRequest, CursorListRequest, CursorListResponse } from '../types';
|
|
4
4
|
/**
|
|
5
5
|
* API Keys API
|
|
6
6
|
*/
|
|
@@ -14,10 +14,7 @@ export declare class ApiKeysAPI {
|
|
|
14
14
|
/**
|
|
15
15
|
* Create an API key
|
|
16
16
|
*/
|
|
17
|
-
create(data:
|
|
18
|
-
name: string;
|
|
19
|
-
scopes?: string[];
|
|
20
|
-
}): Promise<Response<ApiKeyDTO>>;
|
|
17
|
+
create(data: CreateApiKeyRequest): Promise<Response<ApiKeyDTO>>;
|
|
21
18
|
/**
|
|
22
19
|
* Delete an API key
|
|
23
20
|
*/
|
package/dist/api/apps.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpClient } from '../http/client';
|
|
2
2
|
import type { Response } from '../http/response';
|
|
3
|
+
import type { AppPatchBody, AppUpdateBody } from './write-bodies';
|
|
3
4
|
import { AppDTO as App, AppVersionDTO, CursorListRequest, CursorListResponse, LicenseRecordDTO } from '../types';
|
|
4
5
|
/**
|
|
5
6
|
* Apps API
|
|
@@ -24,9 +25,15 @@ export declare class AppsAPI {
|
|
|
24
25
|
*/
|
|
25
26
|
create(data: Partial<App>): Promise<Response<App>>;
|
|
26
27
|
/**
|
|
27
|
-
* Update an app
|
|
28
|
+
* Update an app's copy and edit its current version in place (see
|
|
29
|
+
* AppUpdateBody). Status and visibility have their own methods.
|
|
28
30
|
*/
|
|
29
|
-
update(appId: string, data:
|
|
31
|
+
update(appId: string, data: AppUpdateBody): Promise<Response<App>>;
|
|
32
|
+
/**
|
|
33
|
+
* Change some of an app's copy (PATCH): only the fields sent are written,
|
|
34
|
+
* the others keep their value. Editing the version in place: update.
|
|
35
|
+
*/
|
|
36
|
+
patch(appId: string, data: AppPatchBody): Promise<Response<App>>;
|
|
30
37
|
/**
|
|
31
38
|
* Delete an app
|
|
32
39
|
*/
|
package/dist/api/apps.js
CHANGED
|
@@ -30,11 +30,19 @@ export class AppsAPI {
|
|
|
30
30
|
return this.http.request('post', '/apps', { data });
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
|
-
* Update an app
|
|
33
|
+
* Update an app's copy and edit its current version in place (see
|
|
34
|
+
* AppUpdateBody). Status and visibility have their own methods.
|
|
34
35
|
*/
|
|
35
36
|
async update(appId, data) {
|
|
36
37
|
return this.http.request('post', `/apps/${appId}`, { data });
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Change some of an app's copy (PATCH): only the fields sent are written,
|
|
41
|
+
* the others keep their value. Editing the version in place: update.
|
|
42
|
+
*/
|
|
43
|
+
async patch(appId, data) {
|
|
44
|
+
return this.http.request('patch', `/apps/${appId}`, { data });
|
|
45
|
+
}
|
|
38
46
|
/**
|
|
39
47
|
* Delete an app
|
|
40
48
|
*/
|