opencode-ext-connector 0.3.3 → 0.5.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 +34 -0
- package/README.md +165 -28
- package/dist/core/options.d.ts +20 -0
- package/dist/core/options.js +66 -5
- package/dist/opencode/host-options.d.ts +9 -1
- package/dist/opencode/host-options.js +27 -7
- package/dist/opencode/language-factory.d.ts +1 -1
- package/dist/opencode/language-factory.js +3 -0
- package/dist/opencode/ollama-probe.d.ts +2 -1
- package/dist/opencode/ollama-probe.js +2 -2
- package/dist/opencode/ollama-production.d.ts +7 -0
- package/dist/opencode/ollama-production.js +19 -7
- package/dist/opencode/provider-entry.d.ts +1 -0
- package/dist/opencode/providers.d.ts +2 -0
- package/dist/opencode/providers.js +12 -5
- package/dist/opencode/v1-catalog.js +6 -1
- package/dist/opencode/v1-language.js +5 -2
- package/dist/opencode/v1-session-auth.d.ts +2 -1
- package/dist/opencode/v1-session-auth.js +7 -6
- package/dist/process/production-supervisor.d.ts +20 -0
- package/dist/process/production-supervisor.js +171 -0
- package/dist/providers/claude/credential-authority-scheduler.d.ts +23 -0
- package/dist/providers/claude/credential-authority-scheduler.js +181 -0
- package/dist/providers/command-code/language-model.js +46 -39
- package/dist/providers/command-code/open-generation.d.ts +16 -0
- package/dist/providers/command-code/open-generation.js +45 -0
- package/dist/providers/cursor/credential-retry.d.ts +14 -0
- package/dist/providers/cursor/credential-retry.js +26 -0
- package/dist/providers/cursor/direct-run-failure.d.ts +8 -0
- package/dist/providers/cursor/direct-run-failure.js +26 -0
- package/dist/providers/cursor/direct-run-types.d.ts +1 -0
- package/dist/providers/cursor/direct-run.js +40 -27
- package/dist/providers/cursor/direct-runtime.js +26 -39
- package/dist/providers/cursor/run-session.js +1 -1
- package/dist/providers/ollama/adapter.d.ts +2 -0
- package/dist/providers/ollama/adapter.js +3 -1
- package/dist/providers/ollama/endpoints.d.ts +8 -0
- package/dist/providers/ollama/endpoints.js +38 -0
- package/dist/providers/ollama/index.d.ts +1 -0
- package/dist/providers/ollama/index.js +1 -0
- package/dist/providers/ollama/language-model.d.ts +2 -0
- package/dist/providers/ollama/language-model.js +2 -0
- package/dist/providers/ollama/local-catalog.d.ts +2 -1
- package/dist/providers/ollama/local-catalog.js +3 -3
- package/dist/providers/ollama/runtime.d.ts +2 -0
- package/dist/providers/ollama/runtime.js +5 -5
- package/dist/sdk/ollama.d.ts +4 -1
- package/dist/server.js +55 -13
- package/docs/README.ko.md +165 -28
- package/package.json +1 -1
|
@@ -2,32 +2,32 @@
|
|
|
2
2
|
// Licensed under MIT. See THIRD_PARTY_NOTICES.md.
|
|
3
3
|
import { AdapterError, OperationCancelledError } from "../../core/errors.js";
|
|
4
4
|
import { parseProviderId } from "../../core/ids.js";
|
|
5
|
-
import { openHttpBody } from "../../http/read-body.js";
|
|
6
5
|
import { createCommandCodeVersionResolver } from "./cli-version.js";
|
|
7
6
|
import { emitCommandCodeChunks } from "./emit-stream.js";
|
|
8
7
|
import { commandCodeMissingBodyError } from "./errors.js";
|
|
8
|
+
import { openCommandCodeGeneration } from "./open-generation.js";
|
|
9
9
|
import { buildBody, buildHeaders, } from "./request.js";
|
|
10
|
-
import {
|
|
10
|
+
import { createCommandCodeRequestLifecycle } from "./request-lifecycle.js";
|
|
11
11
|
import { createCommandCodeSessionId } from "./session.js";
|
|
12
|
-
function buildRequestOptions(options
|
|
13
|
-
const
|
|
14
|
-
modelId: options.modelId,
|
|
15
|
-
call,
|
|
16
|
-
sessionId: options.sessionId,
|
|
17
|
-
};
|
|
12
|
+
function buildRequestOptions(options) {
|
|
13
|
+
const { runtime, call, token, cliVersion, bodySnapshot } = options;
|
|
18
14
|
const headerOptions = {
|
|
19
15
|
token,
|
|
20
16
|
cliVersion,
|
|
21
|
-
sessionId:
|
|
17
|
+
sessionId: runtime.sessionId,
|
|
22
18
|
};
|
|
19
|
+
const headers = {};
|
|
20
|
+
for (const source of [buildHeaders(headerOptions), runtime.headers ?? {}, call.headers ?? {}]) {
|
|
21
|
+
for (const [name, value] of Object.entries(source)) {
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
headers[name.toLowerCase()] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
23
27
|
return {
|
|
24
|
-
url: `${(
|
|
25
|
-
headers
|
|
26
|
-
|
|
27
|
-
...options.headers,
|
|
28
|
-
...Object.fromEntries(Object.entries(call.headers ?? {}).filter((entry) => entry[1] !== undefined)),
|
|
29
|
-
},
|
|
30
|
-
body: new TextEncoder().encode(JSON.stringify(buildBody(bodyOptions))),
|
|
28
|
+
url: `${(runtime.baseURL ?? "https://api.commandcode.ai").replace(/\/+$/, "")}/alpha/generate`,
|
|
29
|
+
headers,
|
|
30
|
+
body: new Uint8Array(bodySnapshot),
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
function createUsage() {
|
|
@@ -66,34 +66,41 @@ async function streamCommandCode(options, call) {
|
|
|
66
66
|
providerId: parseProviderId("command-code"),
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
const bodyOptions = {
|
|
70
|
+
modelId: options.modelId,
|
|
71
|
+
call,
|
|
72
|
+
sessionId: options.sessionId,
|
|
73
|
+
};
|
|
74
|
+
const bodySnapshot = new TextEncoder().encode(JSON.stringify(buildBody(bodyOptions)));
|
|
75
|
+
let generation;
|
|
71
76
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
generation = await openCommandCodeGeneration({
|
|
78
|
+
transport: options.transport,
|
|
79
|
+
lifecycle,
|
|
80
|
+
initialToken: token,
|
|
81
|
+
readAccessToken: options.readAccessToken,
|
|
82
|
+
createRequest: (accessToken) => {
|
|
83
|
+
const request = buildRequestOptions({
|
|
84
|
+
runtime: options,
|
|
85
|
+
call,
|
|
86
|
+
token: accessToken,
|
|
87
|
+
cliVersion,
|
|
88
|
+
bodySnapshot,
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
method: "POST",
|
|
92
|
+
url: request.url,
|
|
93
|
+
headers: request.headers,
|
|
94
|
+
body: request.body,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
78
98
|
}
|
|
79
99
|
catch (error) {
|
|
80
100
|
lifecycle.dispose();
|
|
81
101
|
throw error;
|
|
82
102
|
}
|
|
83
|
-
|
|
84
|
-
let errorBody;
|
|
85
|
-
try {
|
|
86
|
-
errorBody = await readCommandCodeErrorBody(opened.chunks);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
lifecycle.abort();
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
finally {
|
|
93
|
-
lifecycle.dispose();
|
|
94
|
-
}
|
|
95
|
-
throw commandCodeHttpError(opened.status, errorBody);
|
|
96
|
-
}
|
|
103
|
+
const { opened, request } = generation;
|
|
97
104
|
if (!opened.bodyPresent) {
|
|
98
105
|
lifecycle.dispose();
|
|
99
106
|
throw commandCodeMissingBodyError(opened.status);
|
|
@@ -124,7 +131,7 @@ async function streamCommandCode(options, call) {
|
|
|
124
131
|
});
|
|
125
132
|
return {
|
|
126
133
|
stream,
|
|
127
|
-
request: { body: new TextDecoder().decode(
|
|
134
|
+
request: { body: request.body === null ? undefined : new TextDecoder().decode(request.body) },
|
|
128
135
|
response: { headers: opened.headers },
|
|
129
136
|
};
|
|
130
137
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HttpRequest, HttpTransport } from "../../core/http.js";
|
|
2
|
+
import { type HttpBodyStream } from "../../http/read-body.js";
|
|
3
|
+
import type { CommandCodeRequestLifecycle } from "./request-lifecycle.js";
|
|
4
|
+
type OpenGenerationOptions = {
|
|
5
|
+
readonly transport: HttpTransport;
|
|
6
|
+
readonly lifecycle: CommandCodeRequestLifecycle;
|
|
7
|
+
readonly initialToken: string;
|
|
8
|
+
readonly readAccessToken: (signal: AbortSignal) => Promise<string | null>;
|
|
9
|
+
readonly createRequest: (token: string) => HttpRequest;
|
|
10
|
+
};
|
|
11
|
+
export type OpenedCommandCodeGeneration = {
|
|
12
|
+
readonly opened: HttpBodyStream;
|
|
13
|
+
readonly request: HttpRequest;
|
|
14
|
+
};
|
|
15
|
+
export declare function openCommandCodeGeneration(options: OpenGenerationOptions): Promise<OpenedCommandCodeGeneration>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Derived from brent-weatherall/opencode-commandcode-provider src/model.ts.
|
|
2
|
+
// Licensed under MIT. See THIRD_PARTY_NOTICES.md.
|
|
3
|
+
import { OperationCancelledError } from "../../core/errors.js";
|
|
4
|
+
import { openHttpBody } from "../../http/read-body.js";
|
|
5
|
+
import { commandCodeHttpError, readCommandCodeErrorBody } from "./request-lifecycle.js";
|
|
6
|
+
async function openAttempt(options, request) {
|
|
7
|
+
const opened = await openHttpBody(options.transport, request, options.lifecycle.signal);
|
|
8
|
+
return { opened, request };
|
|
9
|
+
}
|
|
10
|
+
async function responseError(opened, lifecycle) {
|
|
11
|
+
try {
|
|
12
|
+
const body = await readCommandCodeErrorBody(opened.chunks);
|
|
13
|
+
return commandCodeHttpError(opened.status, body);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
lifecycle.abort();
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function successful(opened) {
|
|
21
|
+
return opened.status >= 200 && opened.status < 300;
|
|
22
|
+
}
|
|
23
|
+
export async function openCommandCodeGeneration(options) {
|
|
24
|
+
const firstRequest = options.createRequest(options.initialToken);
|
|
25
|
+
const first = await openAttempt(options, firstRequest);
|
|
26
|
+
if (successful(first.opened))
|
|
27
|
+
return first;
|
|
28
|
+
const firstError = await responseError(first.opened, options.lifecycle);
|
|
29
|
+
if (first.opened.status !== 401)
|
|
30
|
+
throw firstError;
|
|
31
|
+
const reloadedToken = await options.readAccessToken(options.lifecycle.signal);
|
|
32
|
+
if (options.lifecycle.signal.aborted) {
|
|
33
|
+
throw new OperationCancelledError("command-code-stream");
|
|
34
|
+
}
|
|
35
|
+
if (reloadedToken === null)
|
|
36
|
+
throw firstError;
|
|
37
|
+
const candidateRequest = options.createRequest(reloadedToken);
|
|
38
|
+
if (candidateRequest.headers["authorization"] === firstRequest.headers["authorization"]) {
|
|
39
|
+
throw firstError;
|
|
40
|
+
}
|
|
41
|
+
const second = await openAttempt(options, candidateRequest);
|
|
42
|
+
if (successful(second.opened))
|
|
43
|
+
return second;
|
|
44
|
+
throw await responseError(second.opened, options.lifecycle);
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CursorReplayState } from "./recovery.js";
|
|
2
|
+
export declare function isCursorCredentialRetryEligible(input: {
|
|
3
|
+
readonly error: unknown;
|
|
4
|
+
readonly replay: CursorReplayState;
|
|
5
|
+
readonly retryUsed: boolean;
|
|
6
|
+
}): boolean;
|
|
7
|
+
export type CursorCredentialRetry = {
|
|
8
|
+
readonly currentToken: () => string;
|
|
9
|
+
readonly reload: (error: unknown, replay: CursorReplayState, signal: AbortSignal) => Promise<boolean>;
|
|
10
|
+
};
|
|
11
|
+
export declare function createCursorCredentialRetry(options: {
|
|
12
|
+
readonly initialToken: string;
|
|
13
|
+
readonly reloadAccessToken: (signal: AbortSignal) => Promise<string | null>;
|
|
14
|
+
}): CursorCredentialRetry;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CursorDirectStreamError } from "./direct-stream.js";
|
|
2
|
+
export function isCursorCredentialRetryEligible(input) {
|
|
3
|
+
return (!input.retryUsed &&
|
|
4
|
+
input.error instanceof CursorDirectStreamError &&
|
|
5
|
+
input.error.bridgeCode === "http-401" &&
|
|
6
|
+
input.replay.outputEpoch === 0 &&
|
|
7
|
+
input.replay.checkpointEpoch === null &&
|
|
8
|
+
!input.replay.toolBoundary);
|
|
9
|
+
}
|
|
10
|
+
export function createCursorCredentialRetry(options) {
|
|
11
|
+
let currentToken = options.initialToken;
|
|
12
|
+
let retryUsed = false;
|
|
13
|
+
return {
|
|
14
|
+
currentToken: () => currentToken,
|
|
15
|
+
reload: async (error, replay, signal) => {
|
|
16
|
+
if (!isCursorCredentialRetryEligible({ error, replay, retryUsed }))
|
|
17
|
+
return false;
|
|
18
|
+
retryUsed = true;
|
|
19
|
+
const reloadedToken = await options.reloadAccessToken(signal);
|
|
20
|
+
if (reloadedToken === null || reloadedToken === currentToken)
|
|
21
|
+
return false;
|
|
22
|
+
currentToken = reloadedToken;
|
|
23
|
+
return true;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CursorDirectSetupCleanup } from "./direct-run-types.js";
|
|
2
|
+
import type { CursorRunSession } from "./run-session.js";
|
|
3
|
+
import type { CursorStreamAdapter } from "./stream-adapter.js";
|
|
4
|
+
export declare function retireCursorSessionForRetry(session: CursorRunSession, cause: unknown): Promise<void>;
|
|
5
|
+
export declare function failCursorDirectRun(logical: {
|
|
6
|
+
readonly adapter: CursorStreamAdapter;
|
|
7
|
+
readonly cleanup: CursorDirectSetupCleanup;
|
|
8
|
+
}, session: CursorRunSession | null, error: unknown): Promise<void>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export async function retireCursorSessionForRetry(session, cause) {
|
|
2
|
+
try {
|
|
3
|
+
const retireForRetry = session.retireForRetry;
|
|
4
|
+
if (retireForRetry === undefined)
|
|
5
|
+
throw new TypeError("retry retirement is unavailable");
|
|
6
|
+
await retireForRetry();
|
|
7
|
+
}
|
|
8
|
+
catch (retirementError) {
|
|
9
|
+
throw new AggregateError([cause, retirementError], "Cursor retry retirement failed");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function failCursorDirectRun(logical, session, error) {
|
|
13
|
+
try {
|
|
14
|
+
if (session === null) {
|
|
15
|
+
logical.cleanup.invalidateCheckpoint();
|
|
16
|
+
logical.cleanup.invalidateSession();
|
|
17
|
+
}
|
|
18
|
+
else
|
|
19
|
+
await session.abort();
|
|
20
|
+
}
|
|
21
|
+
catch (cleanupError) {
|
|
22
|
+
logical.adapter.fail(new AggregateError([error, cleanupError], "Cursor recovery cleanup failed"));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
logical.adapter.fail(error);
|
|
26
|
+
}
|
|
@@ -25,6 +25,7 @@ export type CursorDirectRunOptions = {
|
|
|
25
25
|
readonly createSetupCleanup?: (resources: CursorDirectSetupCleanupResources) => CursorDirectSetupCleanup;
|
|
26
26
|
readonly idleTimeoutMs: number;
|
|
27
27
|
readonly modelId: string;
|
|
28
|
+
readonly reloadAccessToken: (signal: AbortSignal) => Promise<string | null>;
|
|
28
29
|
readonly registry: CursorRunSessionRegistry;
|
|
29
30
|
readonly signal: AbortSignal;
|
|
30
31
|
readonly token: string;
|
|
@@ -3,6 +3,8 @@ import { parseModelId } from "../../core/ids.js";
|
|
|
3
3
|
import { createCursorBlobStore } from "./blob-store.js";
|
|
4
4
|
import { createCursorCheckpointStore } from "./checkpoint-store.js";
|
|
5
5
|
import { encodeConnectFrame } from "./connect-frame.js";
|
|
6
|
+
import { createCursorCredentialRetry } from "./credential-retry.js";
|
|
7
|
+
import { failCursorDirectRun, retireCursorSessionForRetry } from "./direct-run-failure.js";
|
|
6
8
|
import { consumeCursorDirectAttempt, isCursorRetryableStreamError } from "./direct-stream.js";
|
|
7
9
|
import { cursorMcpDefinitions } from "./exec-reply.js";
|
|
8
10
|
import { cursorPromptText } from "./prompt.js";
|
|
@@ -76,13 +78,13 @@ function buildRequest(options, logical, mode) {
|
|
|
76
78
|
},
|
|
77
79
|
});
|
|
78
80
|
}
|
|
79
|
-
async function openAttempt(options, logical,
|
|
80
|
-
const request = buildRequest(options, logical, mode);
|
|
81
|
+
async function openAttempt(options, logical, attempt) {
|
|
82
|
+
const request = buildRequest(options, logical, attempt.mode);
|
|
81
83
|
let stream = null;
|
|
82
84
|
try {
|
|
83
85
|
stream = await options.bridge.open({
|
|
84
86
|
id: options.createId(),
|
|
85
|
-
accessToken:
|
|
87
|
+
accessToken: attempt.token,
|
|
86
88
|
path: CURSOR_RUN_PATH,
|
|
87
89
|
headers: { "content-type": "application/connect+proto", "connect-protocol-version": "1" },
|
|
88
90
|
signal: options.signal,
|
|
@@ -123,27 +125,19 @@ async function openAttempt(options, logical, mode) {
|
|
|
123
125
|
throw error;
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
|
-
async function failRun(logical, session, error) {
|
|
127
|
-
try {
|
|
128
|
-
if (session === null) {
|
|
129
|
-
logical.cleanup.invalidateCheckpoint();
|
|
130
|
-
logical.cleanup.invalidateSession();
|
|
131
|
-
}
|
|
132
|
-
else
|
|
133
|
-
await session.abort();
|
|
134
|
-
}
|
|
135
|
-
catch (cleanupError) {
|
|
136
|
-
logical.adapter.fail(new AggregateError([error, cleanupError], "Cursor recovery cleanup failed"));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
logical.adapter.fail(error);
|
|
140
|
-
}
|
|
141
128
|
export async function startCursorDirectRun(options) {
|
|
142
129
|
const logical = createLogicalRun(options);
|
|
143
130
|
const planner = createCursorRecoveryPlanner();
|
|
131
|
+
const credential = createCursorCredentialRetry({
|
|
132
|
+
initialToken: options.token,
|
|
133
|
+
reloadAccessToken: options.reloadAccessToken,
|
|
134
|
+
});
|
|
144
135
|
let session;
|
|
145
136
|
try {
|
|
146
|
-
session = await openAttempt(options, logical,
|
|
137
|
+
session = await openAttempt(options, logical, {
|
|
138
|
+
mode: "initial",
|
|
139
|
+
token: credential.currentToken(),
|
|
140
|
+
});
|
|
147
141
|
}
|
|
148
142
|
catch (error) {
|
|
149
143
|
logical.cleanup.releaseOwnership();
|
|
@@ -177,7 +171,25 @@ export async function startCursorDirectRun(options) {
|
|
|
177
171
|
const idle = watchdog.expired();
|
|
178
172
|
watchdog.dispose();
|
|
179
173
|
if (options.signal.aborted) {
|
|
180
|
-
await
|
|
174
|
+
await failCursorDirectRun(logical, session, new OperationCancelledError("cursor-direct-stream"));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const changed = await credential.reload(error, logical.adapter.replayState(), options.signal);
|
|
179
|
+
if (options.signal.aborted)
|
|
180
|
+
throw new OperationCancelledError("cursor-direct-stream");
|
|
181
|
+
if (changed) {
|
|
182
|
+
await retireCursorSessionForRetry(session, error);
|
|
183
|
+
session = null;
|
|
184
|
+
session = await openAttempt(options, logical, {
|
|
185
|
+
mode: "initial",
|
|
186
|
+
token: credential.currentToken(),
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (retryError) {
|
|
192
|
+
await failCursorDirectRun(logical, session, retryError);
|
|
181
193
|
return;
|
|
182
194
|
}
|
|
183
195
|
const decision = planner.next({
|
|
@@ -190,20 +202,21 @@ export async function startCursorDirectRun(options) {
|
|
|
190
202
|
planner.requireRetry(decision, error);
|
|
191
203
|
}
|
|
192
204
|
catch (recoveryError) {
|
|
193
|
-
await
|
|
205
|
+
await failCursorDirectRun(logical, session, recoveryError);
|
|
194
206
|
}
|
|
195
207
|
return;
|
|
196
208
|
}
|
|
197
209
|
try {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
throw new TypeError("retry retirement is unavailable");
|
|
201
|
-
await retireForRetry();
|
|
210
|
+
await retireCursorSessionForRetry(session, error);
|
|
211
|
+
session = null;
|
|
202
212
|
logical.adapter.suspendForRetry();
|
|
203
|
-
session = await openAttempt(options, logical,
|
|
213
|
+
session = await openAttempt(options, logical, {
|
|
214
|
+
mode: decision.mode,
|
|
215
|
+
token: credential.currentToken(),
|
|
216
|
+
});
|
|
204
217
|
}
|
|
205
218
|
catch (retryError) {
|
|
206
|
-
await
|
|
219
|
+
await failCursorDirectRun(logical, session, retryError);
|
|
207
220
|
return;
|
|
208
221
|
}
|
|
209
222
|
}
|
|
@@ -41,7 +41,9 @@ export function createCursorDirectRuntime(options) {
|
|
|
41
41
|
return bridgeClient;
|
|
42
42
|
};
|
|
43
43
|
const doStream = async (call, modelId) => {
|
|
44
|
-
const signal = call.abortSignal
|
|
44
|
+
const signal = call.abortSignal === undefined
|
|
45
|
+
? lifecycle.signal
|
|
46
|
+
: AbortSignal.any([lifecycle.signal, call.abortSignal]);
|
|
45
47
|
if (signal.aborted)
|
|
46
48
|
throw new OperationCancelledError("cursor-direct-stream");
|
|
47
49
|
const resultCallIds = [];
|
|
@@ -59,46 +61,31 @@ export function createCursorDirectRuntime(options) {
|
|
|
59
61
|
await existing.writeContinuations(continuations, signal);
|
|
60
62
|
return { stream: await consumeCursorDirectSession({ session: existing, signal, registry }) };
|
|
61
63
|
}
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
modelId,
|
|
85
|
-
registry,
|
|
86
|
-
signal,
|
|
87
|
-
token,
|
|
88
|
-
tools: toolsFromCall(call),
|
|
89
|
-
ttlMs: options.ttlMs ?? 300_000,
|
|
90
|
-
...(options.createSetupCleanup === undefined
|
|
91
|
-
? {}
|
|
92
|
-
: { createSetupCleanup: options.createSetupCleanup }),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
lifecycle.signal.removeEventListener("abort", cancel);
|
|
97
|
-
call.abortSignal?.removeEventListener("abort", cancel);
|
|
98
|
-
}
|
|
64
|
+
const token = await options.readAccessToken(signal);
|
|
65
|
+
if (signal.aborted)
|
|
66
|
+
throw new OperationCancelledError("cursor-direct-stream");
|
|
67
|
+
if (token === null)
|
|
68
|
+
throw cursorFailure("cursor-auth-unavailable");
|
|
69
|
+
return await startCursorDirectRun({
|
|
70
|
+
bridge: await bridge(),
|
|
71
|
+
call,
|
|
72
|
+
clock: options.clock,
|
|
73
|
+
createId,
|
|
74
|
+
idleTimeoutMs: options.idleTimeoutMs ?? 60_000,
|
|
75
|
+
modelId,
|
|
76
|
+
reloadAccessToken: options.readAccessToken,
|
|
77
|
+
registry,
|
|
78
|
+
signal,
|
|
79
|
+
token,
|
|
80
|
+
tools: toolsFromCall(call),
|
|
81
|
+
ttlMs: options.ttlMs ?? 300_000,
|
|
82
|
+
...(options.createSetupCleanup === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { createSetupCleanup: options.createSetupCleanup }),
|
|
85
|
+
});
|
|
99
86
|
};
|
|
100
87
|
const disposal = createAsyncDisposable(async () => {
|
|
101
|
-
lifecycle.abort();
|
|
88
|
+
lifecycle.abort(new OperationCancelledError("cursor-direct-stream"));
|
|
102
89
|
await settleCursorCleanup([
|
|
103
90
|
registry.dispose,
|
|
104
91
|
async () => {
|
|
@@ -150,12 +150,12 @@ export function createCursorRunSessionRegistry(_options) {
|
|
|
150
150
|
retireForRetry: async () => {
|
|
151
151
|
if (resources.dispatcher.parkedCalls.size > 0 || reserved.size > 0)
|
|
152
152
|
throw new CursorRunSessionError("retry-boundary");
|
|
153
|
-
retired = true;
|
|
154
153
|
cancelTimer();
|
|
155
154
|
sessions.delete(identity.sessionId);
|
|
156
155
|
removeOwnership(identity.sessionId);
|
|
157
156
|
resources.dispatcher.parkedCalls.clear();
|
|
158
157
|
await settleCursorCleanup([resources.stream.abort, resources.ownership.release]);
|
|
158
|
+
retired = true;
|
|
159
159
|
},
|
|
160
160
|
dispose: disposal.dispose,
|
|
161
161
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "../../core/adapter.js";
|
|
2
2
|
import type { OllamaCatalogState } from "./catalog-state.js";
|
|
3
|
+
import { type OllamaEndpoints } from "./endpoints.js";
|
|
3
4
|
import type { OllamaFetch } from "./http.js";
|
|
4
5
|
export type OllamaAdapterOptions = {
|
|
5
6
|
readonly fetch: OllamaFetch;
|
|
6
7
|
readonly catalog: OllamaCatalogState;
|
|
8
|
+
readonly endpoints?: OllamaEndpoints;
|
|
7
9
|
};
|
|
8
10
|
export declare function createOllamaAdapter(options: OllamaAdapterOptions): ProviderAdapter;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OperationCancelledError } from "../../core/errors.js";
|
|
2
2
|
import { parseProviderId } from "../../core/ids.js";
|
|
3
3
|
import { createAsyncDisposable } from "../../core/lifecycle.js";
|
|
4
|
+
import { parseOllamaEndpoints } from "./endpoints.js";
|
|
4
5
|
import { OllamaCatalogError } from "./errors.js";
|
|
5
6
|
import { listLocalOllamaModels } from "./local-catalog.js";
|
|
6
7
|
function mergeModels(local, cloud) {
|
|
@@ -23,6 +24,7 @@ function catalogFailure(error) {
|
|
|
23
24
|
}
|
|
24
25
|
export function createOllamaAdapter(options) {
|
|
25
26
|
const providerId = parseProviderId("ollama");
|
|
27
|
+
const endpoints = options.endpoints ?? parseOllamaEndpoints(undefined);
|
|
26
28
|
const lease = options.catalog.acquire();
|
|
27
29
|
const disposal = createAsyncDisposable(() => lease.dispose());
|
|
28
30
|
let lastMergedModels = null;
|
|
@@ -33,7 +35,7 @@ export function createOllamaAdapter(options) {
|
|
|
33
35
|
throw new OperationCancelledError("ollama-snapshot");
|
|
34
36
|
let local;
|
|
35
37
|
try {
|
|
36
|
-
local = await listLocalOllamaModels(options.fetch, signal);
|
|
38
|
+
local = await listLocalOllamaModels(options.fetch, signal, endpoints);
|
|
37
39
|
}
|
|
38
40
|
catch (error) {
|
|
39
41
|
const failure = catalogFailure(error);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
|
|
2
|
+
export type OllamaEndpoints = {
|
|
3
|
+
readonly baseURL: string;
|
|
4
|
+
readonly tagsURL: string;
|
|
5
|
+
readonly pullURL: string;
|
|
6
|
+
readonly chatURL: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseOllamaEndpoints(input: unknown): OllamaEndpoints;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
|
|
3
|
+
const DirectApiPaths = ["/api/tags", "/api/pull", "/api/chat"];
|
|
4
|
+
const OllamaBaseURLSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.refine((value) => value.length > 0 &&
|
|
7
|
+
value === value.trim() &&
|
|
8
|
+
/^https?:\/\//iu.test(value) &&
|
|
9
|
+
!/[\t\r\n\\]/u.test(value) &&
|
|
10
|
+
!/^https?:\/\/[^/?#]*@/iu.test(value) &&
|
|
11
|
+
!/[?#]$/u.test(value) &&
|
|
12
|
+
URL.canParse(value))
|
|
13
|
+
.transform((value) => new URL(value))
|
|
14
|
+
.superRefine((url, context) => {
|
|
15
|
+
const cloudHostname = url.hostname.toLowerCase().replace(/\.+$/u, "");
|
|
16
|
+
const path = url.pathname.replace(/\/+$/u, "") || "/";
|
|
17
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") ||
|
|
18
|
+
url.hostname.length === 0 ||
|
|
19
|
+
url.username.length > 0 ||
|
|
20
|
+
url.password.length > 0 ||
|
|
21
|
+
url.search.length > 0 ||
|
|
22
|
+
url.hash.length > 0 ||
|
|
23
|
+
cloudHostname === "ollama.com" ||
|
|
24
|
+
cloudHostname.endsWith(".ollama.com") ||
|
|
25
|
+
DirectApiPaths.some((apiPath) => path.endsWith(apiPath))) {
|
|
26
|
+
context.addIssue({ code: "custom", message: "invalid Ollama daemon base URL" });
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
.transform((url) => `${url.origin}${url.pathname.replace(/\/+$/u, "")}`);
|
|
30
|
+
export function parseOllamaEndpoints(input) {
|
|
31
|
+
const baseURL = OllamaBaseURLSchema.parse(input === undefined ? DEFAULT_OLLAMA_BASE_URL : input);
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
baseURL,
|
|
34
|
+
tagsURL: `${baseURL}/api/tags`,
|
|
35
|
+
pullURL: `${baseURL}/api/pull`,
|
|
36
|
+
chatURL: `${baseURL}/api/chat`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createOllamaAdapter, type OllamaAdapterOptions } from "./adapter.js";
|
|
2
2
|
export { createOllamaCatalogState, type OllamaCatalogLease, type OllamaCatalogState, type OllamaCatalogStateOptions, } from "./catalog-state.js";
|
|
3
3
|
export { discoverOllamaCloudModels } from "./cloud-catalog.js";
|
|
4
|
+
export { DEFAULT_OLLAMA_BASE_URL, type OllamaEndpoints, parseOllamaEndpoints, } from "./endpoints.js";
|
|
4
5
|
export { OllamaCatalogError, type OllamaCatalogErrorKind, OllamaGenerationError, type OllamaGenerationOperation, } from "./errors.js";
|
|
5
6
|
export { type OllamaFetch, productionOllamaFetch } from "./http.js";
|
|
6
7
|
export { createOllamaLanguageModel, type OllamaLanguageModelOptions, } from "./language-model.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createOllamaAdapter } from "./adapter.js";
|
|
2
2
|
export { createOllamaCatalogState, } from "./catalog-state.js";
|
|
3
3
|
export { discoverOllamaCloudModels } from "./cloud-catalog.js";
|
|
4
|
+
export { DEFAULT_OLLAMA_BASE_URL, parseOllamaEndpoints, } from "./endpoints.js";
|
|
4
5
|
export { OllamaCatalogError, OllamaGenerationError, } from "./errors.js";
|
|
5
6
|
export { productionOllamaFetch } from "./http.js";
|
|
6
7
|
export { createOllamaLanguageModel, } from "./language-model.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
2
2
|
import type { OllamaCatalogState } from "./catalog-state.js";
|
|
3
|
+
import { type OllamaEndpoints } from "./endpoints.js";
|
|
3
4
|
import type { OllamaFetch } from "./http.js";
|
|
4
5
|
import { type OllamaRuntime } from "./runtime.js";
|
|
5
6
|
export type OllamaLanguageModelOptions = {
|
|
@@ -7,5 +8,6 @@ export type OllamaLanguageModelOptions = {
|
|
|
7
8
|
readonly runtime?: OllamaRuntime;
|
|
8
9
|
readonly catalog?: OllamaCatalogState;
|
|
9
10
|
readonly fetch?: OllamaFetch;
|
|
11
|
+
readonly endpoints?: OllamaEndpoints;
|
|
10
12
|
};
|
|
11
13
|
export declare function createOllamaLanguageModel(options: OllamaLanguageModelOptions): LanguageModelV3;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OperationCancelledError } from "../../core/errors.js";
|
|
2
|
+
import { parseOllamaEndpoints } from "./endpoints.js";
|
|
2
3
|
import { generateFromOllamaStream } from "./generate.js";
|
|
3
4
|
import { buildOllamaCall } from "./prompt.js";
|
|
4
5
|
import { createOllamaRuntime } from "./runtime.js";
|
|
@@ -10,6 +11,7 @@ function runtimeFromOptions(options) {
|
|
|
10
11
|
throw new TypeError("Ollama catalog state is required");
|
|
11
12
|
return createOllamaRuntime({
|
|
12
13
|
catalog: options.catalog,
|
|
14
|
+
endpoints: options.endpoints ?? parseOllamaEndpoints(undefined),
|
|
13
15
|
...(options.fetch === undefined ? {} : { fetch: options.fetch }),
|
|
14
16
|
});
|
|
15
17
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { AdapterModel } from "../../core/models.js";
|
|
2
|
+
import { type OllamaEndpoints } from "./endpoints.js";
|
|
2
3
|
import { type OllamaFetch } from "./http.js";
|
|
3
|
-
export declare function listLocalOllamaModels(fetch: OllamaFetch, signal: AbortSignal): Promise<readonly AdapterModel[]>;
|
|
4
|
+
export declare function listLocalOllamaModels(fetch: OllamaFetch, signal: AbortSignal, endpoints?: OllamaEndpoints): Promise<readonly AdapterModel[]>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { parseAdapterModel } from "../../core/models.js";
|
|
3
|
+
import { parseOllamaEndpoints } from "./endpoints.js";
|
|
3
4
|
import { OllamaCatalogError } from "./errors.js";
|
|
4
5
|
import { requestOllamaCatalog } from "./http.js";
|
|
5
|
-
const LOCAL_TAGS_URL = "http://localhost:11434/api/tags";
|
|
6
6
|
const LocalModelSchema = z
|
|
7
7
|
.object({ model: z.string().optional(), name: z.string().optional() })
|
|
8
8
|
.superRefine((value, context) => {
|
|
@@ -17,9 +17,9 @@ const LocalModelSchema = z
|
|
|
17
17
|
})
|
|
18
18
|
.transform(({ model, name }) => (model?.length === 0 ? undefined : model) ?? name ?? "");
|
|
19
19
|
const LocalTagsSchema = z.object({ models: z.array(LocalModelSchema) }).readonly();
|
|
20
|
-
export async function listLocalOllamaModels(fetch, signal) {
|
|
20
|
+
export async function listLocalOllamaModels(fetch, signal, endpoints = parseOllamaEndpoints(undefined)) {
|
|
21
21
|
const text = await requestOllamaCatalog({
|
|
22
|
-
url:
|
|
22
|
+
url: endpoints.tagsURL,
|
|
23
23
|
accept: "application/json",
|
|
24
24
|
operation: "local-tags",
|
|
25
25
|
fetch,
|