@vibesdotdev/code-sessions 0.0.0-stage → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +165 -2
- package/dist/agent/coordinator.agent.descriptor.d.ts +69 -0
- package/dist/agent/coordinator.agent.descriptor.d.ts.map +1 -0
- package/dist/agent/coordinator.agent.descriptor.js +60 -0
- package/dist/agent/coordinator.agent.descriptor.js.map +1 -0
- package/dist/agent/coordinator.prompt.descriptor.d.ts +135 -0
- package/dist/agent/coordinator.prompt.descriptor.d.ts.map +1 -0
- package/dist/agent/coordinator.prompt.descriptor.js +29 -0
- package/dist/agent/coordinator.prompt.descriptor.js.map +1 -0
- package/dist/agent/coordinator.system-prompt.d.ts +3 -0
- package/dist/agent/coordinator.system-prompt.d.ts.map +1 -0
- package/dist/agent/coordinator.system-prompt.js +18 -0
- package/dist/agent/coordinator.system-prompt.js.map +1 -0
- package/dist/agent/developer.agent.descriptor.d.ts +74 -0
- package/dist/agent/developer.agent.descriptor.d.ts.map +1 -0
- package/dist/agent/developer.agent.descriptor.js +138 -0
- package/dist/agent/developer.agent.descriptor.js.map +1 -0
- package/dist/agent/developer.prompt.descriptor.d.ts +137 -0
- package/dist/agent/developer.prompt.descriptor.d.ts.map +1 -0
- package/dist/agent/developer.prompt.descriptor.js +29 -0
- package/dist/agent/developer.prompt.descriptor.js.map +1 -0
- package/dist/agent/reviewer.agent.descriptor.d.ts +69 -0
- package/dist/agent/reviewer.agent.descriptor.d.ts.map +1 -0
- package/dist/agent/reviewer.agent.descriptor.js +60 -0
- package/dist/agent/reviewer.agent.descriptor.js.map +1 -0
- package/dist/agent/reviewer.prompt.descriptor.d.ts +135 -0
- package/dist/agent/reviewer.prompt.descriptor.d.ts.map +1 -0
- package/dist/agent/reviewer.prompt.descriptor.js +29 -0
- package/dist/agent/reviewer.prompt.descriptor.js.map +1 -0
- package/dist/agent/reviewer.system-prompt.d.ts +3 -0
- package/dist/agent/reviewer.system-prompt.d.ts.map +1 -0
- package/dist/agent/reviewer.system-prompt.js +17 -0
- package/dist/agent/reviewer.system-prompt.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +12 -0
- package/dist/agent/system-prompt.d.ts.map +1 -0
- package/dist/agent/system-prompt.js +28 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/api/code-sessions.api-client.descriptor.d.ts +25 -0
- package/dist/api/code-sessions.api-client.descriptor.d.ts.map +1 -0
- package/dist/api/code-sessions.api-client.descriptor.js +26 -0
- package/dist/api/code-sessions.api-client.descriptor.js.map +1 -0
- package/dist/api/code-sessions.api-client.impl.d.ts +156 -0
- package/dist/api/code-sessions.api-client.impl.d.ts.map +1 -0
- package/dist/api/code-sessions.api-client.impl.js +852 -0
- package/dist/api/code-sessions.api-client.impl.js.map +1 -0
- package/dist/api/code-sessions.api-client.schemas.d.ts +2 -0
- package/dist/api/code-sessions.api-client.schemas.d.ts.map +1 -0
- package/dist/api/code-sessions.api-client.schemas.js +2 -0
- package/dist/api/code-sessions.api-client.schemas.js.map +1 -0
- package/dist/api/code-sessions.api-client.types.d.ts +252 -0
- package/dist/api/code-sessions.api-client.types.d.ts.map +1 -0
- package/dist/api/code-sessions.api-client.types.js +77 -0
- package/dist/api/code-sessions.api-client.types.js.map +1 -0
- package/dist/api/index.d.ts +13 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +20 -0
- package/dist/api/index.js.map +1 -0
- package/dist/capability.d.ts +9 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +9 -0
- package/dist/capability.js.map +1 -0
- package/dist/catalog/index.d.ts +2 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +2 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog/session-catalog.d.ts +131 -0
- package/dist/catalog/session-catalog.d.ts.map +1 -0
- package/dist/catalog/session-catalog.impl.consumer.worker.d.ts +113 -0
- package/dist/catalog/session-catalog.impl.consumer.worker.d.ts.map +1 -0
- package/dist/catalog/session-catalog.impl.consumer.worker.js +32 -0
- package/dist/catalog/session-catalog.impl.consumer.worker.js.map +1 -0
- package/dist/catalog/session-catalog.js +126 -0
- package/dist/catalog/session-catalog.js.map +1 -0
- package/dist/clock/clock.client.d.ts +18 -0
- package/dist/clock/clock.client.d.ts.map +1 -0
- package/dist/clock/clock.client.js +43 -0
- package/dist/clock/clock.client.js.map +1 -0
- package/dist/code-sessions.consumer.plugin.d.ts +4 -0
- package/dist/code-sessions.consumer.plugin.d.ts.map +1 -0
- package/dist/code-sessions.consumer.plugin.js +16 -0
- package/dist/code-sessions.consumer.plugin.js.map +1 -0
- package/dist/code-sessions.plugin.d.ts +15 -0
- package/dist/code-sessions.plugin.d.ts.map +1 -0
- package/dist/code-sessions.plugin.js +65 -0
- package/dist/code-sessions.plugin.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +9 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/preparation.d.ts +2 -0
- package/dist/config/preparation.d.ts.map +1 -0
- package/dist/config/preparation.js +2 -0
- package/dist/config/preparation.js.map +1 -0
- package/dist/docs/code-sessions.docs.descriptor.d.ts +4 -0
- package/dist/docs/code-sessions.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/code-sessions.docs.descriptor.js +241 -0
- package/dist/docs/code-sessions.docs.descriptor.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/pull-request-plan.d.ts +68 -0
- package/dist/pull-request-plan.d.ts.map +1 -0
- package/dist/pull-request-plan.js +94 -0
- package/dist/pull-request-plan.js.map +1 -0
- package/dist/schemas/preparation.d.ts +51 -0
- package/dist/schemas/preparation.d.ts.map +1 -0
- package/dist/schemas/preparation.js +20 -0
- package/dist/schemas/preparation.js.map +1 -0
- package/dist/schemas/requests.d.ts +411 -0
- package/dist/schemas/requests.d.ts.map +1 -0
- package/dist/schemas/requests.js +87 -0
- package/dist/schemas/requests.js.map +1 -0
- package/dist/schemas/responses.d.ts +275 -0
- package/dist/schemas/responses.d.ts.map +1 -0
- package/dist/schemas/responses.js +90 -0
- package/dist/schemas/responses.js.map +1 -0
- package/dist/svelte/actions.d.ts +70 -0
- package/dist/svelte/actions.d.ts.map +1 -0
- package/dist/svelte/actions.js +280 -0
- package/dist/svelte/clock.svelte.d.ts +6 -0
- package/dist/svelte/clock.svelte.d.ts.map +1 -0
- package/dist/svelte/clock.svelte.js +33 -0
- package/dist/svelte/components/CreateSessionDialog.svelte +470 -0
- package/dist/svelte/components/CreateSessionDialog.svelte.d.ts +57 -0
- package/dist/svelte/components/CreateSessionDialog.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SessionCard.svelte +216 -0
- package/dist/svelte/components/SessionCard.svelte.d.ts +27 -0
- package/dist/svelte/components/SessionCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SessionList.svelte +86 -0
- package/dist/svelte/components/SessionList.svelte.d.ts +23 -0
- package/dist/svelte/components/SessionList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/SessionSeed.svelte +60 -0
- package/dist/svelte/components/SessionSeed.svelte.d.ts +9 -0
- package/dist/svelte/components/SessionSeed.svelte.d.ts.map +1 -0
- package/dist/svelte/components/TranscriptView.svelte +360 -0
- package/dist/svelte/components/TranscriptView.svelte.d.ts +26 -0
- package/dist/svelte/components/TranscriptView.svelte.d.ts.map +1 -0
- package/dist/svelte/create-session-form.d.ts +84 -0
- package/dist/svelte/create-session-form.d.ts.map +1 -0
- package/dist/svelte/create-session-form.js +107 -0
- package/dist/svelte/index.d.ts +20 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +18 -0
- package/dist/svelte/live-session.d.ts +156 -0
- package/dist/svelte/live-session.d.ts.map +1 -0
- package/dist/svelte/live-session.js +152 -0
- package/dist/svelte/loaders.d.ts +74 -0
- package/dist/svelte/loaders.d.ts.map +1 -0
- package/dist/svelte/loaders.js +124 -0
- package/dist/svelte/outcome.d.ts +46 -0
- package/dist/svelte/outcome.d.ts.map +1 -0
- package/dist/svelte/outcome.js +49 -0
- package/dist/svelte/relative-time.d.ts +32 -0
- package/dist/svelte/relative-time.d.ts.map +1 -0
- package/dist/svelte/relative-time.js +51 -0
- package/dist/svelte/session-list-item.d.ts +23 -0
- package/dist/svelte/session-list-item.d.ts.map +1 -0
- package/dist/svelte/session-list-item.js +1 -0
- package/dist/svelte/styles.css +8 -0
- package/dist/svelte/transcript-input.d.ts +45 -0
- package/dist/svelte/transcript-input.d.ts.map +1 -0
- package/dist/svelte/transcript-input.js +188 -0
- package/dist/tui/code-sessions-tui.plugin.d.ts +10 -0
- package/dist/tui/code-sessions-tui.plugin.d.ts.map +1 -0
- package/dist/tui/code-sessions-tui.plugin.js +29 -0
- package/dist/tui/code-sessions-tui.plugin.js.map +1 -0
- package/dist/tui/code-sessions.tui-app.descriptor.d.ts +7 -0
- package/dist/tui/code-sessions.tui-app.descriptor.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-app.descriptor.js +30 -0
- package/dist/tui/code-sessions.tui-app.descriptor.js.map +1 -0
- package/dist/tui/code-sessions.tui-app.impl.consumer.d.ts +14 -0
- package/dist/tui/code-sessions.tui-app.impl.consumer.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-app.impl.consumer.js +453 -0
- package/dist/tui/code-sessions.tui-app.impl.consumer.js.map +1 -0
- package/dist/tui/code-sessions.tui-client.d.ts +22 -0
- package/dist/tui/code-sessions.tui-client.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-client.js +95 -0
- package/dist/tui/code-sessions.tui-client.js.map +1 -0
- package/dist/tui/code-sessions.tui-open.descriptor.d.ts +7 -0
- package/dist/tui/code-sessions.tui-open.descriptor.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-open.descriptor.js +16 -0
- package/dist/tui/code-sessions.tui-open.descriptor.js.map +1 -0
- package/dist/tui/code-sessions.tui-open.impl.consumer.d.ts +7 -0
- package/dist/tui/code-sessions.tui-open.impl.consumer.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-open.impl.consumer.js +11 -0
- package/dist/tui/code-sessions.tui-open.impl.consumer.js.map +1 -0
- package/dist/tui/code-sessions.tui-state.descriptor.d.ts +16 -0
- package/dist/tui/code-sessions.tui-state.descriptor.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-state.descriptor.js +50 -0
- package/dist/tui/code-sessions.tui-state.descriptor.js.map +1 -0
- package/dist/tui/code-sessions.tui-state.impl.consumer.d.ts +30 -0
- package/dist/tui/code-sessions.tui-state.impl.consumer.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-state.impl.consumer.js +533 -0
- package/dist/tui/code-sessions.tui-state.impl.consumer.js.map +1 -0
- package/dist/tui/code-sessions.tui-state.key.d.ts +322 -0
- package/dist/tui/code-sessions.tui-state.key.d.ts.map +1 -0
- package/dist/tui/code-sessions.tui-state.key.js +104 -0
- package/dist/tui/code-sessions.tui-state.key.js.map +1 -0
- package/dist/tui/index.d.ts +9 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +8 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/workspace-binding.d.ts +74 -0
- package/dist/workspace/workspace-binding.d.ts.map +1 -0
- package/dist/workspace/workspace-binding.impl.consumer.worker.d.ts +17 -0
- package/dist/workspace/workspace-binding.impl.consumer.worker.d.ts.map +1 -0
- package/dist/workspace/workspace-binding.impl.consumer.worker.js +134 -0
- package/dist/workspace/workspace-binding.impl.consumer.worker.js.map +1 -0
- package/dist/workspace/workspace-binding.js +72 -0
- package/dist/workspace/workspace-binding.js.map +1 -0
- package/package.json +238 -4
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Sessions API Client — universal HTTP implementation.
|
|
3
|
+
*
|
|
4
|
+
* Cross-origin HTTP client against `${platformOrigin}/api/code-sessions/*`.
|
|
5
|
+
* Loaded in cli/browser/sveltekit/cloud scopes by the code-sessions plugin
|
|
6
|
+
* loader.
|
|
7
|
+
*
|
|
8
|
+
* Origin: explicit override, else the `platform` infra/service catalog origin.
|
|
9
|
+
*
|
|
10
|
+
* Authentication: `Authorization: Bearer <user-jwt>` (cli/ssr/cross-app), or
|
|
11
|
+
* same-origin `credentials: 'include'` in the browser. SSR never forwards a
|
|
12
|
+
* request cookie as a credential.
|
|
13
|
+
*
|
|
14
|
+
* Failures are typed (`CapabilityError` code and reason): a platform error
|
|
15
|
+
* status is a `CodeSessionsApiError` whose body is a bounded, redacted excerpt;
|
|
16
|
+
* an unreachable platform, an unreadable or malformed answer and every caller
|
|
17
|
+
* refusal carry their own reason. Every success body is parsed at this
|
|
18
|
+
* boundary before a caller sees it. Diagnostics go through the handed
|
|
19
|
+
* runtime's logger only.
|
|
20
|
+
*
|
|
21
|
+
* NEVER logs the bearer token.
|
|
22
|
+
*/
|
|
23
|
+
import { BaseRuntimeClient } from '@vibesdotdev/kernel/client';
|
|
24
|
+
import { CapabilityError, CaughtFailureSchema, isCapabilityError } from '@vibesdotdev/kernel';
|
|
25
|
+
import { serviceOrigin } from '@vibesdotdev/infra-core/service';
|
|
26
|
+
import { resolveCallerBearer } from '@vibesdotdev/auth/caller-bearer';
|
|
27
|
+
import { filterCodeExecutionHosts } from '@vibesdotdev/platform/hosts/schemas';
|
|
28
|
+
import { AgentCodeSessionDescriptorSchema, resolveCodeSession } from '@vibesdotdev/agent-execution/code-session';
|
|
29
|
+
import { getLogger } from '@vibesdotdev/logging';
|
|
30
|
+
import { requestBoundary, CodeSessionCreateSchema, RunCommandRequestSchema, PushBranchRequestSchema, CreatePullRequestRequestSchema, SessionIdSchema, CodeSessionLinkSchema, ServiceAssertionSchema, HttpOptionsSchema, ListOptionsSchema, StartOptionsRequestSchema } from "../schemas/requests.js";
|
|
31
|
+
import { resolveSessionCapability } from "../capability.js";
|
|
32
|
+
import { attempt } from "../svelte/outcome.js";
|
|
33
|
+
import { codeSessionPreparationSchema } from "../config/preparation.js";
|
|
34
|
+
import { CodeSessionListEnvelopeSchema, CodeSessionRecordEnvelopeSchema, CreatePullRequestResponseSchema, PreparationEnvelopeSchema, PushBranchResponseSchema, RunCommandResponseSchema } from "./code-sessions.api-client.schemas.js";
|
|
35
|
+
import { CodeSessionsApiError, CodeSessionsNotSignedInError } from "./code-sessions.api-client.types.js";
|
|
36
|
+
const LOGGER_ID = 'code-sessions:api-client';
|
|
37
|
+
const SERVICE_PREPARE_ROUTE = '/api/internal/code-sessions/prepare';
|
|
38
|
+
function withLink(links, link) {
|
|
39
|
+
return [
|
|
40
|
+
...(links ?? []).filter((candidate) => candidate.kind !== link.kind || candidate.id !== link.id || candidate.source !== link.source),
|
|
41
|
+
link
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
/** True for the one resolution failure that means "no signed-in caller". */
|
|
45
|
+
function isNotSignedIn(failure) {
|
|
46
|
+
return isCapabilityError(failure) && failure.code === 'AUTH_REQUIRED' && failure.reason === 'not-signed-in';
|
|
47
|
+
}
|
|
48
|
+
async function resolveOrigin(runtime) {
|
|
49
|
+
return (await serviceOrigin(runtime, 'platform')).replace(/\/$/, '');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether a browser runtime may ride its page's same-origin Platform session
|
|
53
|
+
* cookies. Only a browser surface does, and never the packaged Desktop
|
|
54
|
+
* renderer (`cef`), which renders from `file://` with neither a same-origin
|
|
55
|
+
* Platform session nor permission to probe the network speculatively. Cloud
|
|
56
|
+
* functionality is admitted there later through an explicit bearer-backed
|
|
57
|
+
* runtime client; local sessions remain independently available offline.
|
|
58
|
+
*/
|
|
59
|
+
export function canUseBrowserSessionCookies(scope) {
|
|
60
|
+
return scope.surface === 'browser' && scope.qualifiers.cef !== true;
|
|
61
|
+
}
|
|
62
|
+
/** One plain record of the caller's headers, names as given (a `Headers` instance reads lower-cased). */
|
|
63
|
+
function headersInitToRecord(headers) {
|
|
64
|
+
if (headers instanceof Headers)
|
|
65
|
+
return Object.fromEntries(headers);
|
|
66
|
+
if (Array.isArray(headers))
|
|
67
|
+
return Object.fromEntries(headers.map(([name, value]) => [name, value]));
|
|
68
|
+
return Object.fromEntries(Object.entries(headers ?? {}));
|
|
69
|
+
}
|
|
70
|
+
function hasHeader(record, name) {
|
|
71
|
+
const lower = name.toLowerCase();
|
|
72
|
+
return Object.keys(record).some((key) => key.toLowerCase() === lower);
|
|
73
|
+
}
|
|
74
|
+
function omitCookieHeaders(headers) {
|
|
75
|
+
return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== 'cookie'));
|
|
76
|
+
}
|
|
77
|
+
/** Last layer wins; names are matched case-insensitively so `Authorization` replaces `authorization`. */
|
|
78
|
+
function mergeHeaders(...layers) {
|
|
79
|
+
const byName = new Map();
|
|
80
|
+
for (const layer of layers) {
|
|
81
|
+
for (const [key, value] of Object.entries(layer ?? {}))
|
|
82
|
+
byName.set(key.toLowerCase(), [key, value]);
|
|
83
|
+
}
|
|
84
|
+
return Object.fromEntries(byName.values());
|
|
85
|
+
}
|
|
86
|
+
function annotate(record) {
|
|
87
|
+
return {
|
|
88
|
+
...record,
|
|
89
|
+
capability: resolveSessionCapability(record.hostTarget),
|
|
90
|
+
execution: 'platform'
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function workspaceName(path) {
|
|
94
|
+
return (path
|
|
95
|
+
.replace(/[\\/]+$/, '')
|
|
96
|
+
.split(/[\\/]/)
|
|
97
|
+
.pop() || path);
|
|
98
|
+
}
|
|
99
|
+
function localRecordFromHandle(handle) {
|
|
100
|
+
const timestamp = handle.createdAt ?? new Date().toISOString();
|
|
101
|
+
return {
|
|
102
|
+
sessionId: handle.sessionId,
|
|
103
|
+
repositoryId: handle.repositoryId ?? null,
|
|
104
|
+
repositoryName: handle.workspaceOwnership === 'borrowed' ? workspaceName(handle.workspacePath) : null,
|
|
105
|
+
ephemeral: handle.ephemeral,
|
|
106
|
+
hostTarget: handle.hostTarget,
|
|
107
|
+
runtimeConfig: {},
|
|
108
|
+
status: 'active',
|
|
109
|
+
previewUrl: handle.previewUrl ?? null,
|
|
110
|
+
workspacePath: handle.workspacePath,
|
|
111
|
+
checkoutRef: null,
|
|
112
|
+
ownerType: 'local',
|
|
113
|
+
ownerId: null,
|
|
114
|
+
createdAt: timestamp,
|
|
115
|
+
lastActivityAt: timestamp,
|
|
116
|
+
capability: 'interactive',
|
|
117
|
+
execution: 'local'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function targetFromDescriptor(descriptor) {
|
|
121
|
+
const hostTarget = descriptor.hostTarget;
|
|
122
|
+
if (!hostTarget)
|
|
123
|
+
return null;
|
|
124
|
+
const execution = hostTarget.kind === 'host' && hostTarget.hostId === 'local' ? 'local' : 'platform';
|
|
125
|
+
return {
|
|
126
|
+
id: `${execution}:${descriptor.id}`,
|
|
127
|
+
name: execution === 'local'
|
|
128
|
+
? 'This machine'
|
|
129
|
+
: hostTarget.kind === 'cloud'
|
|
130
|
+
? 'Vibes cloud sandbox'
|
|
131
|
+
: descriptor.name,
|
|
132
|
+
description: execution === 'local'
|
|
133
|
+
? 'Runs in the current TUI process on this machine.'
|
|
134
|
+
: (descriptor.description ?? `Runs with ${descriptor.name}.`),
|
|
135
|
+
hostTarget,
|
|
136
|
+
execution
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** A refused caller input: `INVALID_INPUT` with its reason. */
|
|
140
|
+
function invalidInput(message, reason) {
|
|
141
|
+
return new CapabilityError(message, 'INVALID_INPUT', { reason });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Runtime port `VibesRuntime` (Runtime SPEC `./client`): the caller bearer
|
|
145
|
+
* reads `declaredScope`, which `RuntimeRef` does not carry, directly and
|
|
146
|
+
* through auth's `resolveCallerBearer` port.
|
|
147
|
+
*/
|
|
148
|
+
export class CodeSessionsApiClientConsumer extends BaseRuntimeClient {
|
|
149
|
+
originOverride = null;
|
|
150
|
+
tokenOverride = undefined;
|
|
151
|
+
httpOverrides = {};
|
|
152
|
+
serviceAssertion;
|
|
153
|
+
localSessions = new Map();
|
|
154
|
+
localSessionInFlight = null;
|
|
155
|
+
startOptionsInFlight = null;
|
|
156
|
+
constructor(descriptor = { id: 'code-sessions', kind: 'runtime/client' }, context, runtime) {
|
|
157
|
+
super(descriptor, context, runtime);
|
|
158
|
+
}
|
|
159
|
+
configureHttp(value) {
|
|
160
|
+
return this.configureHttpValidated(requestBoundary(HttpOptionsSchema).parse(value));
|
|
161
|
+
}
|
|
162
|
+
configureHttpValidated(overrides) {
|
|
163
|
+
if (overrides.origin !== undefined) {
|
|
164
|
+
this.originOverride = overrides.origin.replace(/\/$/, '');
|
|
165
|
+
}
|
|
166
|
+
if ('token' in overrides) {
|
|
167
|
+
this.tokenOverride = overrides.token ?? null;
|
|
168
|
+
}
|
|
169
|
+
this.httpOverrides = {
|
|
170
|
+
fetch: overrides.fetch,
|
|
171
|
+
headers: overrides.headers,
|
|
172
|
+
credentials: overrides.credentials
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/** A request-scoped copy of this client on the same runtime. */
|
|
176
|
+
fork(overrides) {
|
|
177
|
+
const forked = new CodeSessionsApiClientConsumer(this.descriptor, undefined, this.runtime);
|
|
178
|
+
// Read the configured origin only when the caller does not supply it: an
|
|
179
|
+
// explicit value never depends on the host's environment. An explicit
|
|
180
|
+
// token (the caller's or this client's) carries over; otherwise the fork
|
|
181
|
+
// resolves the caller's bearer itself, per request.
|
|
182
|
+
const next = { ...overrides };
|
|
183
|
+
if (next.origin === undefined && this.originOverride !== null)
|
|
184
|
+
next.origin = this.originOverride;
|
|
185
|
+
if (!('token' in overrides) && this.tokenOverride !== undefined)
|
|
186
|
+
next.token = this.tokenOverride;
|
|
187
|
+
if (this.serviceAssertion) {
|
|
188
|
+
next.fetch ??= this.httpOverrides.fetch;
|
|
189
|
+
next.token = null;
|
|
190
|
+
next.headers = {};
|
|
191
|
+
next.credentials = 'omit';
|
|
192
|
+
}
|
|
193
|
+
forked.configureHttp(next);
|
|
194
|
+
forked.serviceAssertion = this.serviceAssertion;
|
|
195
|
+
return forked;
|
|
196
|
+
}
|
|
197
|
+
forRequest(value) {
|
|
198
|
+
return this.fork(requestBoundary(HttpOptionsSchema).parse(value));
|
|
199
|
+
}
|
|
200
|
+
forServiceRequest(input) {
|
|
201
|
+
return this.forServiceRequestValidated(requestBoundary(ServiceAssertionSchema, 'service-identity-invalid').parse(input));
|
|
202
|
+
}
|
|
203
|
+
forServiceRequestValidated(assertion) {
|
|
204
|
+
const { userId, organizationId } = assertion;
|
|
205
|
+
const forked = this.fork({
|
|
206
|
+
token: null,
|
|
207
|
+
headers: {},
|
|
208
|
+
credentials: 'omit',
|
|
209
|
+
fetch: this.httpOverrides.fetch
|
|
210
|
+
});
|
|
211
|
+
const serviceAssertion = { userId };
|
|
212
|
+
if (organizationId)
|
|
213
|
+
serviceAssertion.organizationId = organizationId;
|
|
214
|
+
forked.serviceAssertion = serviceAssertion;
|
|
215
|
+
return forked;
|
|
216
|
+
}
|
|
217
|
+
async prepare(input) {
|
|
218
|
+
return this.prepareValidated(requestBoundary(codeSessionPreparationSchema, 'preparation-input-invalid').parse(input));
|
|
219
|
+
}
|
|
220
|
+
async prepareValidated(data) {
|
|
221
|
+
if (!data.repositoryId && !data.createRepositoryIfMissing) {
|
|
222
|
+
throw invalidInput('Choose a repository or allow this Chat to create one.', 'repository-required');
|
|
223
|
+
}
|
|
224
|
+
const route = this.serviceAssertion ? SERVICE_PREPARE_ROUTE : '/api/code-sessions/prepare';
|
|
225
|
+
const payload = await this.requestJson('POST', route, PreparationEnvelopeSchema, data);
|
|
226
|
+
return payload.result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* True when a bearer is configured or the caller's bearer resolves. A
|
|
230
|
+
* signed-out caller is `false`; any other resolution failure propagates.
|
|
231
|
+
*/
|
|
232
|
+
async isAuthenticated() {
|
|
233
|
+
return (await this.signedInBearer()) !== null;
|
|
234
|
+
}
|
|
235
|
+
noSessionHint() {
|
|
236
|
+
return new CodeSessionsNotSignedInError().message;
|
|
237
|
+
}
|
|
238
|
+
async canAttemptPlatformRequest() {
|
|
239
|
+
if (await this.signedInBearer())
|
|
240
|
+
return true;
|
|
241
|
+
// An explicitly configured origin marks an HTTP/request-scoped client.
|
|
242
|
+
// Its transport admission never depends on an ambient page global.
|
|
243
|
+
if (this.originOverride !== null)
|
|
244
|
+
return true;
|
|
245
|
+
return canUseBrowserSessionCookies(this.runtime.declaredScope);
|
|
246
|
+
}
|
|
247
|
+
async localSession() {
|
|
248
|
+
this.localSessionInFlight ??= this.resolveLocalSession();
|
|
249
|
+
return this.localSessionInFlight;
|
|
250
|
+
}
|
|
251
|
+
async resolveLocalSession() {
|
|
252
|
+
// Confirmed absence: a runtime that registers no code-session kind has no local plane.
|
|
253
|
+
if (!this.runtime.hasKind('agent/code-session'))
|
|
254
|
+
return null;
|
|
255
|
+
const resolved = await attempt(() => resolveCodeSession(this.runtime, { requiredHostTargetKind: 'host' }));
|
|
256
|
+
if (resolved.ok)
|
|
257
|
+
return resolved.value;
|
|
258
|
+
// Declared-optional: no resolvable local execution target means no local session.
|
|
259
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
260
|
+
logger.warn('local code session unavailable', { error: resolved.failure });
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
async platformClient() {
|
|
264
|
+
if (!(await this.canAttemptPlatformRequest()))
|
|
265
|
+
return null;
|
|
266
|
+
const resolved = await attempt(() => this.runtime.query('runtime/client').withId('platform').resolve());
|
|
267
|
+
if (!resolved.ok) {
|
|
268
|
+
// Declared-optional: the platform client is degraded (`degraded: ['platform']`), not fatal.
|
|
269
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
270
|
+
logger.warn('platform client unavailable', { error: resolved.failure });
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
const base = resolved.value;
|
|
274
|
+
const token = await this.signedInBearer();
|
|
275
|
+
const overrideHeaders = omitCookieHeaders(headersInitToRecord(this.httpOverrides.headers));
|
|
276
|
+
const bearer = {};
|
|
277
|
+
if (token && !hasHeader(overrideHeaders, 'authorization'))
|
|
278
|
+
bearer.Authorization = `Bearer ${token}`;
|
|
279
|
+
return base.forRequest({
|
|
280
|
+
origin: await this.resolveConfiguredOrigin(),
|
|
281
|
+
fetch: this.httpOverrides.fetch,
|
|
282
|
+
headers: mergeHeaders(bearer, overrideHeaders)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async rememberLocalHandle(handle, session) {
|
|
286
|
+
const impl = session ?? (await this.localSession());
|
|
287
|
+
if (!impl)
|
|
288
|
+
return null;
|
|
289
|
+
const projected = localRecordFromHandle(handle);
|
|
290
|
+
const existingLinks = this.localSessions.get(handle.sessionId)?.record.links;
|
|
291
|
+
const entry = {
|
|
292
|
+
record: existingLinks ? { ...projected, links: existingLinks } : projected,
|
|
293
|
+
session: impl
|
|
294
|
+
};
|
|
295
|
+
this.localSessions.set(handle.sessionId, entry);
|
|
296
|
+
return entry;
|
|
297
|
+
}
|
|
298
|
+
async discoverLocalSessions() {
|
|
299
|
+
const session = await this.localSession();
|
|
300
|
+
if (!session?.discover)
|
|
301
|
+
return;
|
|
302
|
+
for (const handle of await session.discover()) {
|
|
303
|
+
await this.rememberLocalHandle(handle, session);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async findLocalSession(sessionId) {
|
|
307
|
+
const known = this.localSessions.get(sessionId);
|
|
308
|
+
if (known)
|
|
309
|
+
return known;
|
|
310
|
+
const session = await this.localSession();
|
|
311
|
+
if (!session)
|
|
312
|
+
return null;
|
|
313
|
+
const attached = await attempt(() => session.attach(sessionId));
|
|
314
|
+
if (!attached.ok) {
|
|
315
|
+
// A local implementation that cannot attach an id has answered
|
|
316
|
+
// "not mine" — a MISS, never a failure of the lookup. Implementations
|
|
317
|
+
// word that refusal differently (the relay impl says `'<id>' is not a
|
|
318
|
+
// relay session id`), so a text match would send every cloud session
|
|
319
|
+
// viewed on a relay-resolving surface into a throw. The platform is the
|
|
320
|
+
// authoritative store for a session this client did not mint locally,
|
|
321
|
+
// so the miss falls through to it; the decline is reported here once.
|
|
322
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
323
|
+
logger.warn('local attach declined; consulting the platform', { sessionId, error: attached.failure });
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
return this.rememberLocalHandle(attached.value, session);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* The host's workspace, offered as the "current workspace" source. A host
|
|
330
|
+
* that binds no workspace (C1) offers none; any other failure propagates.
|
|
331
|
+
*/
|
|
332
|
+
async runtimeWorkspacePath() {
|
|
333
|
+
try {
|
|
334
|
+
return await this.runtime.context('runtime/workspace-path');
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
338
|
+
// Confirmed absence of exactly this context, and only its unbound form.
|
|
339
|
+
if (CapabilityError.isAbsenceOf(failure, 'context', 'runtime/workspace-path') &&
|
|
340
|
+
isCapabilityError(failure) &&
|
|
341
|
+
failure.reason === 'workspace-unbound') {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
throw failure;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async resolveStartTarget(descriptor) {
|
|
348
|
+
const resolved = await attempt(() => this.runtime.query('agent/code-session').withId(descriptor.id).resolve());
|
|
349
|
+
if (resolved.ok)
|
|
350
|
+
return true;
|
|
351
|
+
// Declared-optional: an unresolvable target is omitted from the options.
|
|
352
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
353
|
+
logger.warn('code-session target did not resolve', { id: descriptor.id, error: resolved.failure });
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
async runtimeStartTargets() {
|
|
357
|
+
if (!this.runtime.hasKind('agent/code-session'))
|
|
358
|
+
return [];
|
|
359
|
+
const byExecutionTarget = new Map();
|
|
360
|
+
for (const registered of this.runtime.query('agent/code-session').descriptors()) {
|
|
361
|
+
const parsed = AgentCodeSessionDescriptorSchema.safeParse(registered);
|
|
362
|
+
if (!parsed.success) {
|
|
363
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
364
|
+
logger.warn('code-session target descriptor is malformed; omitted', { error: parsed.error });
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const descriptor = parsed.data;
|
|
368
|
+
if (descriptor.enabled === false)
|
|
369
|
+
continue;
|
|
370
|
+
const target = targetFromDescriptor(descriptor);
|
|
371
|
+
if (!target)
|
|
372
|
+
continue;
|
|
373
|
+
if (!(await this.resolveStartTarget(descriptor)))
|
|
374
|
+
continue;
|
|
375
|
+
const key = `${target.execution}:${target.hostTarget.kind}:${target.hostTarget.kind === 'host' ? target.hostTarget.hostId : 'cloud'}`;
|
|
376
|
+
if (!byExecutionTarget.has(key))
|
|
377
|
+
byExecutionTarget.set(key, target);
|
|
378
|
+
}
|
|
379
|
+
return [...byExecutionTarget.values()];
|
|
380
|
+
}
|
|
381
|
+
async getStartOptions(value) {
|
|
382
|
+
this.assertStandardAccess();
|
|
383
|
+
return this.getStartOptionsValidated(requestBoundary(StartOptionsRequestSchema).parse(value));
|
|
384
|
+
}
|
|
385
|
+
async getStartOptionsValidated(options) {
|
|
386
|
+
if (!options.force && this.startOptionsInFlight)
|
|
387
|
+
return this.startOptionsInFlight;
|
|
388
|
+
const load = async () => {
|
|
389
|
+
const degraded = [];
|
|
390
|
+
const targets = await this.runtimeStartTargets();
|
|
391
|
+
if (targets.length === 0)
|
|
392
|
+
degraded.push('runtime');
|
|
393
|
+
const platform = await this.platformClient();
|
|
394
|
+
let repositories = [];
|
|
395
|
+
if (await this.canAttemptPlatformRequest()) {
|
|
396
|
+
if (!platform) {
|
|
397
|
+
degraded.push('platform');
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
const [hostsResult, repositoriesResult] = await Promise.allSettled([
|
|
401
|
+
platform.hosts.list({ status: 'active', limit: 200, offset: 0 }),
|
|
402
|
+
platform.repositories.list({ status: 'active', limit: 100, offset: 0 })
|
|
403
|
+
]);
|
|
404
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
405
|
+
if (hostsResult.status === 'fulfilled') {
|
|
406
|
+
for (const host of filterCodeExecutionHosts(hostsResult.value.results ?? [])) {
|
|
407
|
+
const id = `platform:host:${host.hostId}`;
|
|
408
|
+
if (targets.some((target) => target.id === id))
|
|
409
|
+
continue;
|
|
410
|
+
targets.push({
|
|
411
|
+
id,
|
|
412
|
+
name: host.name?.trim() || host.hostId,
|
|
413
|
+
description: [
|
|
414
|
+
host.relayReachable ? 'Connected fleet host' : 'Active fleet host',
|
|
415
|
+
host.region
|
|
416
|
+
]
|
|
417
|
+
.filter(Boolean)
|
|
418
|
+
.join(' · '),
|
|
419
|
+
hostTarget: { kind: 'host', hostId: host.hostId },
|
|
420
|
+
execution: 'platform'
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
logger.warn('platform host list failed', { error: hostsResult.reason });
|
|
426
|
+
degraded.push('hosts');
|
|
427
|
+
}
|
|
428
|
+
if (repositoriesResult.status === 'fulfilled') {
|
|
429
|
+
repositories = repositoriesResult.value.results ?? [];
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
logger.warn('platform repository list failed', { error: repositoriesResult.reason });
|
|
433
|
+
degraded.push('repositories');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const localTargetIds = targets
|
|
438
|
+
.filter((target) => target.execution === 'local')
|
|
439
|
+
.map((target) => target.id);
|
|
440
|
+
const platformTargetIds = targets
|
|
441
|
+
.filter((target) => target.execution === 'platform')
|
|
442
|
+
.map((target) => target.id);
|
|
443
|
+
const workspacePath = await this.runtimeWorkspacePath();
|
|
444
|
+
const sources = [];
|
|
445
|
+
if (workspacePath && localTargetIds.length > 0) {
|
|
446
|
+
sources.push({
|
|
447
|
+
id: 'workspace:current',
|
|
448
|
+
name: workspaceName(workspacePath),
|
|
449
|
+
description: `Current directory · ${workspacePath}`,
|
|
450
|
+
compatibleTargetIds: localTargetIds,
|
|
451
|
+
workspacePath
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
if (targets.length > 0) {
|
|
455
|
+
sources.push({
|
|
456
|
+
id: 'workspace:ephemeral',
|
|
457
|
+
name: 'New empty workspace',
|
|
458
|
+
description: 'Creates a session-owned temporary Git workspace.',
|
|
459
|
+
compatibleTargetIds: targets.map((target) => target.id),
|
|
460
|
+
ephemeral: true
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
for (const repository of repositories) {
|
|
464
|
+
if (repository.status && repository.status !== 'active')
|
|
465
|
+
continue;
|
|
466
|
+
sources.push({
|
|
467
|
+
id: `repository:${repository.id}`,
|
|
468
|
+
name: repository.name?.trim() || repository.id,
|
|
469
|
+
description: 'Account repository',
|
|
470
|
+
compatibleTargetIds: platformTargetIds,
|
|
471
|
+
repositoryId: repository.id
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const defaultTargetId = localTargetIds[0] ?? targets[0]?.id ?? null;
|
|
475
|
+
const defaultSourceId = sources.find((source) => defaultTargetId ? source.compatibleTargetIds.includes(defaultTargetId) : false)?.id ?? null;
|
|
476
|
+
return { targets, sources, defaultTargetId, defaultSourceId, degraded };
|
|
477
|
+
};
|
|
478
|
+
const pending = load().finally(() => {
|
|
479
|
+
if (this.startOptionsInFlight === pending)
|
|
480
|
+
this.startOptionsInFlight = null;
|
|
481
|
+
});
|
|
482
|
+
this.startOptionsInFlight = pending;
|
|
483
|
+
return pending;
|
|
484
|
+
}
|
|
485
|
+
async resolveConfiguredOrigin() {
|
|
486
|
+
if (this.originOverride)
|
|
487
|
+
return this.originOverride;
|
|
488
|
+
return resolveOrigin(this.runtime);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* An explicit token wins; a browser host presents none (same-origin
|
|
492
|
+
* credentials); otherwise the caller's bearer from auth's `resolveCallerBearer`.
|
|
493
|
+
*/
|
|
494
|
+
async resolveConfiguredToken() {
|
|
495
|
+
if (this.tokenOverride !== undefined)
|
|
496
|
+
return this.tokenOverride;
|
|
497
|
+
if (this.runtime.declaredScope.surface === 'browser')
|
|
498
|
+
return null;
|
|
499
|
+
return resolveCallerBearer(this.runtime);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* The caller's bearer: a token, `null` when none is configured (a browser or
|
|
503
|
+
* anonymous caller), or the typed signed-out refusal carrying the resolution
|
|
504
|
+
* failure. Any other resolution failure propagates.
|
|
505
|
+
*/
|
|
506
|
+
async callerBearer() {
|
|
507
|
+
try {
|
|
508
|
+
return await this.resolveConfiguredToken();
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
512
|
+
if (isNotSignedIn(failure))
|
|
513
|
+
return new CodeSessionsNotSignedInError({ cause: failure });
|
|
514
|
+
throw failure;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/** The configured bearer, `null` when the caller presents none or is signed out. */
|
|
518
|
+
async signedInBearer() {
|
|
519
|
+
const bearer = await this.callerBearer();
|
|
520
|
+
return bearer instanceof CodeSessionsNotSignedInError ? null : bearer;
|
|
521
|
+
}
|
|
522
|
+
requestCredentials() {
|
|
523
|
+
// A browser rides its session cookies automatically; CLI and SSR use a bearer.
|
|
524
|
+
if (this.httpOverrides.credentials)
|
|
525
|
+
return this.httpOverrides.credentials;
|
|
526
|
+
return this.runtime.declaredScope.surface === 'browser' ? 'include' : undefined;
|
|
527
|
+
}
|
|
528
|
+
/** Refuse before resolving either execution plane or dispatching a request. */
|
|
529
|
+
assertStandardAccess() {
|
|
530
|
+
if (this.serviceAssertion) {
|
|
531
|
+
throw invalidInput('Service code-session clients support owner-verified preparation only.', 'service-client-preparation-only');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* One request to the platform: the response when it is ok, `null` when a
|
|
536
|
+
* failed status is the confirmed absence `absence` declares, otherwise a typed
|
|
537
|
+
* refusal. The bearer and service credential never reach a failure's text.
|
|
538
|
+
*/
|
|
539
|
+
async send(method, path, body, absence = {}) {
|
|
540
|
+
if (path !== SERVICE_PREPARE_ROUTE)
|
|
541
|
+
this.assertStandardAccess();
|
|
542
|
+
const url = `${await this.resolveConfiguredOrigin()}${path}`;
|
|
543
|
+
const fetchFn = this.httpOverrides.fetch ?? fetch;
|
|
544
|
+
const credentialValues = [];
|
|
545
|
+
const headers = mergeHeaders({ Accept: 'application/json' }, body !== undefined ? { 'Content-Type': 'application/json' } : undefined, omitCookieHeaders(headersInitToRecord(this.httpOverrides.headers)));
|
|
546
|
+
// A signed-out caller sends no bearer; a configured origin may still
|
|
547
|
+
// carry the request (`canAttemptPlatformRequest`). An explicit
|
|
548
|
+
// Authorization on the request fork is left intact.
|
|
549
|
+
const token = await this.signedInBearer();
|
|
550
|
+
if (token)
|
|
551
|
+
credentialValues.push(token);
|
|
552
|
+
if (token && !hasHeader(headers, 'authorization'))
|
|
553
|
+
headers.Authorization = `Bearer ${token}`;
|
|
554
|
+
if (this.serviceAssertion) {
|
|
555
|
+
// The service credential comes only from the host's secrets/store (RC05);
|
|
556
|
+
// a typed failure (not set, no store, store failed) propagates as-is.
|
|
557
|
+
const service = await this.runtime.resolveCredential({ source: 'secret', key: 'VIBES_INTERNAL_SERVICE_TOKEN' });
|
|
558
|
+
if (!service.ok)
|
|
559
|
+
throw service.error;
|
|
560
|
+
credentialValues.push(service.value);
|
|
561
|
+
headers.Authorization = `Bearer ${service.value}`;
|
|
562
|
+
headers['x-vibes-user-id'] = this.serviceAssertion.userId;
|
|
563
|
+
if (this.serviceAssertion.organizationId) {
|
|
564
|
+
headers['x-vibes-organization-id'] = this.serviceAssertion.organizationId;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const init = { method, headers };
|
|
568
|
+
const credentials = this.requestCredentials();
|
|
569
|
+
if (credentials)
|
|
570
|
+
init.credentials = credentials;
|
|
571
|
+
if (body !== undefined)
|
|
572
|
+
init.body = JSON.stringify(body);
|
|
573
|
+
let res;
|
|
574
|
+
try {
|
|
575
|
+
res = await fetchFn(url, init);
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
const failure = CaughtFailureSchema.parse(error);
|
|
579
|
+
if (failure instanceof Error && failure.name === 'AbortError') {
|
|
580
|
+
throw new CapabilityError('The code-sessions request was cancelled.', 'CANCELLED', {
|
|
581
|
+
reason: 'request-aborted',
|
|
582
|
+
details: { method },
|
|
583
|
+
cause: failure
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
// The URL (origin, path, any query) stays out of the message; the transport error rides as cause.
|
|
587
|
+
throw new CapabilityError('The platform could not be reached.', 'UNAVAILABLE', {
|
|
588
|
+
reason: 'platform-unreachable',
|
|
589
|
+
details: { method },
|
|
590
|
+
cause: failure
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
if (res.ok)
|
|
594
|
+
return res;
|
|
595
|
+
if (res.status === 401) {
|
|
596
|
+
if (absence.signedOutAsAbsent)
|
|
597
|
+
return null;
|
|
598
|
+
throw new CodeSessionsNotSignedInError();
|
|
599
|
+
}
|
|
600
|
+
if (res.status === 404 && absence.absentOn404)
|
|
601
|
+
return null;
|
|
602
|
+
let text;
|
|
603
|
+
try {
|
|
604
|
+
text = await res.text();
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
throw new CapabilityError('The platform error response could not be read.', 'UNAVAILABLE', {
|
|
608
|
+
reason: 'platform-body-unreadable',
|
|
609
|
+
details: { status: res.status, method },
|
|
610
|
+
cause: CaughtFailureSchema.parse(error)
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
throw new CodeSessionsApiError(`code-sessions ${method} ${path} → ${res.status}`, res.status, text, false, {
|
|
614
|
+
method,
|
|
615
|
+
path,
|
|
616
|
+
redactions: credentialValues
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
/** The response body parsed by `schema`, or the typed `platform-response-invalid` refusal. */
|
|
620
|
+
async readJson(res, schema, method) {
|
|
621
|
+
let payload;
|
|
622
|
+
try {
|
|
623
|
+
payload = await res.json();
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
throw new CapabilityError('The platform returned a response that is not JSON.', 'INTERNAL', {
|
|
627
|
+
reason: 'platform-response-invalid',
|
|
628
|
+
details: { status: res.status, method },
|
|
629
|
+
cause: CaughtFailureSchema.parse(error)
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
const parsed = schema.safeParse(payload);
|
|
633
|
+
if (!parsed.success) {
|
|
634
|
+
throw new CapabilityError('The platform returned an unexpected response.', 'INTERNAL', {
|
|
635
|
+
reason: 'platform-response-invalid',
|
|
636
|
+
details: { status: res.status, method },
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return parsed.data;
|
|
640
|
+
}
|
|
641
|
+
async requestJson(method, path, schema, body) {
|
|
642
|
+
const res = await this.send(method, path, body);
|
|
643
|
+
if (!res) {
|
|
644
|
+
throw new CapabilityError('The platform answered without a response.', 'INTERNAL', {
|
|
645
|
+
reason: 'platform-response-invalid',
|
|
646
|
+
details: { method }
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return this.readJson(res, schema, method);
|
|
650
|
+
}
|
|
651
|
+
async requestJsonUnlessAbsent(method, path, schema, absence) {
|
|
652
|
+
const res = await this.send(method, path, undefined, absence);
|
|
653
|
+
return res ? this.readJson(res, schema, method) : null;
|
|
654
|
+
}
|
|
655
|
+
async list(value) {
|
|
656
|
+
this.assertStandardAccess();
|
|
657
|
+
return this.listValidated(requestBoundary(ListOptionsSchema).parse(value));
|
|
658
|
+
}
|
|
659
|
+
async listValidated(options) {
|
|
660
|
+
await this.discoverLocalSessions();
|
|
661
|
+
const local = [...this.localSessions.values()]
|
|
662
|
+
.map((entry) => entry.record)
|
|
663
|
+
.filter((record) => !options?.status || record.status === options.status);
|
|
664
|
+
if (!(await this.canAttemptPlatformRequest()))
|
|
665
|
+
return local;
|
|
666
|
+
const params = new URLSearchParams();
|
|
667
|
+
if (options?.status)
|
|
668
|
+
params.set('status', options.status);
|
|
669
|
+
const qs = params.toString();
|
|
670
|
+
const path = `/api/code-sessions${qs ? `?${qs}` : ''}`;
|
|
671
|
+
const platform = await attempt(() => this.requestJsonUnlessAbsent('GET', path, CodeSessionListEnvelopeSchema, { signedOutAsAbsent: true }));
|
|
672
|
+
if (!platform.ok) {
|
|
673
|
+
// The local execution plane is independently useful. A cloud transport
|
|
674
|
+
// failure must not hide sessions already running on this machine; callers
|
|
675
|
+
// with no local records still receive the platform failure.
|
|
676
|
+
if (local.length === 0)
|
|
677
|
+
throw platform.failure;
|
|
678
|
+
const logger = getLogger(LOGGER_ID, this.runtime);
|
|
679
|
+
logger.warn('platform code-session list failed; showing local sessions only', { error: platform.failure });
|
|
680
|
+
return local;
|
|
681
|
+
}
|
|
682
|
+
if (!platform.value)
|
|
683
|
+
return local;
|
|
684
|
+
return [...local, ...platform.value.results.map(annotate)];
|
|
685
|
+
}
|
|
686
|
+
async get(value) {
|
|
687
|
+
this.assertStandardAccess();
|
|
688
|
+
const sessionId = requestBoundary(SessionIdSchema).parse(value);
|
|
689
|
+
const local = await this.findLocalSession(sessionId);
|
|
690
|
+
if (local)
|
|
691
|
+
return local.record;
|
|
692
|
+
return this.getOwned(sessionId);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* PLATFORM-AUTHORITATIVE read: never consults the local plane.
|
|
696
|
+
*
|
|
697
|
+
* `get()` is local-plane-first, and the local relay implementation attaches
|
|
698
|
+
* any well-formed relay id via the service credential WITHOUT an ownership
|
|
699
|
+
* check — fine for a surface reading its own machine, but never evidence of
|
|
700
|
+
* ownership. Authorization decisions (e.g. binding an MCP caller to a code
|
|
701
|
+
* session) MUST use this method: the platform's `/api/code-sessions/[id]`
|
|
702
|
+
* route is owner-scoped and answers 404 for a foreign session. Fails closed
|
|
703
|
+
* with null only for absence or signed-out callers; transport failures propagate.
|
|
704
|
+
*/
|
|
705
|
+
async getOwned(value) {
|
|
706
|
+
this.assertStandardAccess();
|
|
707
|
+
return this.getOwnedValidated(requestBoundary(SessionIdSchema).parse(value));
|
|
708
|
+
}
|
|
709
|
+
async getOwnedValidated(sessionId) {
|
|
710
|
+
if (!(await this.canAttemptPlatformRequest()))
|
|
711
|
+
return null;
|
|
712
|
+
const payload = await this.requestJsonUnlessAbsent('GET', `/api/code-sessions/${encodeURIComponent(sessionId)}`, CodeSessionRecordEnvelopeSchema, { absentOn404: true, signedOutAsAbsent: true });
|
|
713
|
+
return payload ? annotate(payload.result) : null;
|
|
714
|
+
}
|
|
715
|
+
async create(value) {
|
|
716
|
+
this.assertStandardAccess();
|
|
717
|
+
return this.createValidated(requestBoundary(CodeSessionCreateSchema).parse(value));
|
|
718
|
+
}
|
|
719
|
+
async createValidated(input) {
|
|
720
|
+
if (input.execution === 'local') {
|
|
721
|
+
const hostTarget = input.hostTarget ?? { kind: 'host', hostId: 'local' };
|
|
722
|
+
if (hostTarget.kind !== 'host' || hostTarget.hostId !== 'local') {
|
|
723
|
+
throw invalidInput('Local code sessions require hostTarget {kind:"host",hostId:"local"}.', 'local-host-target-required');
|
|
724
|
+
}
|
|
725
|
+
const session = await this.localSession();
|
|
726
|
+
if (!session) {
|
|
727
|
+
throw new CapabilityError('No local code-session implementation is registered.', 'NOT_FOUND', {
|
|
728
|
+
reason: 'local-implementation-unregistered'
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
const initialize = { hostTarget };
|
|
732
|
+
if (input.sessionId)
|
|
733
|
+
initialize.sessionId = input.sessionId;
|
|
734
|
+
if (input.workspacePath)
|
|
735
|
+
initialize.workspacePath = input.workspacePath;
|
|
736
|
+
if (input.ephemeral)
|
|
737
|
+
initialize.ephemeral = true;
|
|
738
|
+
if (input.checkoutRef)
|
|
739
|
+
initialize.checkoutRef = input.checkoutRef;
|
|
740
|
+
if (input.runtimeConfig)
|
|
741
|
+
initialize.runtimeConfig = input.runtimeConfig;
|
|
742
|
+
if (input.initialEnv)
|
|
743
|
+
initialize.initialEnv = input.initialEnv;
|
|
744
|
+
if (input.exposedPorts)
|
|
745
|
+
initialize.exposedPorts = input.exposedPorts;
|
|
746
|
+
const handle = await session.initialize(initialize);
|
|
747
|
+
const record = {
|
|
748
|
+
...localRecordFromHandle(handle),
|
|
749
|
+
runtimeConfig: input.runtimeConfig ?? {},
|
|
750
|
+
checkoutRef: input.checkoutRef ?? null
|
|
751
|
+
};
|
|
752
|
+
if (input.links)
|
|
753
|
+
record.links = input.links;
|
|
754
|
+
this.localSessions.set(record.sessionId, { record, session });
|
|
755
|
+
return record;
|
|
756
|
+
}
|
|
757
|
+
const { execution: _execution, ...platformInput } = input;
|
|
758
|
+
const payload = await this.requestJson('POST', '/api/code-sessions', CodeSessionRecordEnvelopeSchema, platformInput);
|
|
759
|
+
return annotate(payload.result);
|
|
760
|
+
}
|
|
761
|
+
async link(value, relationship) {
|
|
762
|
+
this.assertStandardAccess();
|
|
763
|
+
return this.linkValidated(requestBoundary(SessionIdSchema).parse(value), requestBoundary(CodeSessionLinkSchema).parse(relationship));
|
|
764
|
+
}
|
|
765
|
+
async linkValidated(sessionId, link) {
|
|
766
|
+
const local = this.localSessions.get(sessionId);
|
|
767
|
+
if (local) {
|
|
768
|
+
local.record = { ...local.record, links: withLink(local.record.links, link) };
|
|
769
|
+
return local.record;
|
|
770
|
+
}
|
|
771
|
+
const payload = await this.requestJson('PATCH', `/api/code-sessions/${encodeURIComponent(sessionId)}`, CodeSessionRecordEnvelopeSchema, { link });
|
|
772
|
+
return annotate(payload.result);
|
|
773
|
+
}
|
|
774
|
+
async run(value) {
|
|
775
|
+
this.assertStandardAccess();
|
|
776
|
+
const input = requestBoundary(RunCommandRequestSchema).parse(value);
|
|
777
|
+
const { sessionId, ...body } = input;
|
|
778
|
+
const local = await this.findLocalSession(sessionId);
|
|
779
|
+
if (local) {
|
|
780
|
+
const startedAt = new Date();
|
|
781
|
+
const result = await local.session.runCommand(sessionId, body);
|
|
782
|
+
const endedAt = new Date();
|
|
783
|
+
local.record = { ...local.record, lastActivityAt: endedAt.toISOString() };
|
|
784
|
+
return {
|
|
785
|
+
sessionId,
|
|
786
|
+
command: input.command,
|
|
787
|
+
...result,
|
|
788
|
+
startedAt: startedAt.toISOString(),
|
|
789
|
+
endedAt: endedAt.toISOString()
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
return this.requestJson('POST', `/api/code-sessions/${encodeURIComponent(sessionId)}/run`, RunCommandResponseSchema, body);
|
|
793
|
+
}
|
|
794
|
+
async push(value) {
|
|
795
|
+
this.assertStandardAccess();
|
|
796
|
+
return this.pushValidated(requestBoundary(PushBranchRequestSchema).parse(value));
|
|
797
|
+
}
|
|
798
|
+
async pushValidated(input) {
|
|
799
|
+
const { sessionId, ...body } = input;
|
|
800
|
+
// No local-session shortcut on purpose — see the interface note. Publishing
|
|
801
|
+
// must go through the platform so the authority chain and the bound,
|
|
802
|
+
// revocable credential are not bypassable from a client.
|
|
803
|
+
return this.requestJson('POST', `/api/code-sessions/${encodeURIComponent(sessionId)}/push`, PushBranchResponseSchema, body);
|
|
804
|
+
}
|
|
805
|
+
async createPullRequest(value) {
|
|
806
|
+
this.assertStandardAccess();
|
|
807
|
+
return this.createPullRequestValidated(requestBoundary(CreatePullRequestRequestSchema).parse(value));
|
|
808
|
+
}
|
|
809
|
+
async createPullRequestValidated(input) {
|
|
810
|
+
const { sessionId, ...body } = input;
|
|
811
|
+
return this.requestJson('POST', `/api/code-sessions/${encodeURIComponent(sessionId)}/pull-request`, CreatePullRequestResponseSchema, body);
|
|
812
|
+
}
|
|
813
|
+
async delete(value) {
|
|
814
|
+
this.assertStandardAccess();
|
|
815
|
+
return this.deleteValidated(requestBoundary(SessionIdSchema).parse(value));
|
|
816
|
+
}
|
|
817
|
+
async deleteValidated(sessionId) {
|
|
818
|
+
const local = await this.findLocalSession(sessionId);
|
|
819
|
+
if (local) {
|
|
820
|
+
await local.session.teardown(sessionId);
|
|
821
|
+
this.localSessions.delete(sessionId);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
await this.send('DELETE', `/api/code-sessions/${encodeURIComponent(sessionId)}`, undefined);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
export default CodeSessionsApiClientConsumer;
|
|
828
|
+
/**
|
|
829
|
+
* Create a CodeSessionsApiClientConsumer configured for SSR use. It configures
|
|
830
|
+
* the client through the same public `configureHttp` path every request fork
|
|
831
|
+
* uses; it never touches private implementation fields.
|
|
832
|
+
*
|
|
833
|
+
* Usage:
|
|
834
|
+
* const client = createSsrCodeSessionsClient(event.locals.runtime, {
|
|
835
|
+
* origin: platformOrigin,
|
|
836
|
+
* token: event.locals.sessionToken
|
|
837
|
+
* });
|
|
838
|
+
* const sessions = await client.list();
|
|
839
|
+
*/
|
|
840
|
+
export function createSsrCodeSessionsClient(runtime, opts) {
|
|
841
|
+
const client = new CodeSessionsApiClientConsumer({ id: 'code-sessions', kind: 'runtime/client' }, { runtime }, runtime);
|
|
842
|
+
const overrides = {
|
|
843
|
+
token: opts.token,
|
|
844
|
+
fetch: opts.fetch,
|
|
845
|
+
credentials: 'omit'
|
|
846
|
+
};
|
|
847
|
+
if (opts.origin)
|
|
848
|
+
overrides.origin = opts.origin;
|
|
849
|
+
client.configureHttp(overrides);
|
|
850
|
+
return client;
|
|
851
|
+
}
|
|
852
|
+
//# sourceMappingURL=code-sessions.api-client.impl.js.map
|