larkin 0.4.1 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -33
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +218 -228
- package/assets/readme/larkin-github-cover.png +0 -0
- package/assets/readme/larkin-hero.svg +111 -0
- package/dist/agent/agent-state-store.cjs +193 -1
- package/dist/agent/agent-state-store.mjs +191 -0
- package/dist/app/agent-config.mjs +73 -6
- package/dist/app/agent-readiness.cjs +22 -2
- package/dist/app/agent-readiness.mjs +20 -2
- package/dist/app/binary-entry.mjs +6 -0
- package/dist/app/cli.mjs +7 -1
- package/dist/app/local-control.mjs +108 -2
- package/dist/app/run.mjs +8 -0
- package/dist/app/runtime-process.mjs +17 -1
- package/dist/app/session-cli.mjs +55 -20
- package/dist/dashboard/dashboard-view-model.mjs +12 -3
- package/dist/feishu/host-shell.cjs +104 -9
- package/dist/feishu/host-shell.mjs +105 -10
- package/dist/platform/config.cjs +406 -33
- package/dist/platform/config.mjs +405 -33
- package/dist/platform/process-boundary-trace.cjs +108 -0
- package/dist/platform/process-boundary-trace.mjs +102 -0
- package/dist/runtime/pi-artifact-provenance.cjs +147 -0
- package/dist/runtime/pi-artifact-provenance.mjs +139 -0
- package/dist/runtime/pi-compaction-recovery.cjs +703 -0
- package/dist/runtime/pi-compaction-recovery.mjs +679 -0
- package/dist/runtime/pi-model-catalog.cjs +2 -2
- package/dist/runtime/pi-model-catalog.mjs +2 -2
- package/dist/runtime/pi-profile-migration.cjs +556 -0
- package/dist/runtime/pi-profile-migration.mjs +515 -0
- package/dist/runtime/pi-provider-config.cjs +3 -3
- package/dist/runtime/pi-provider-config.mjs +3 -3
- package/dist/runtime/pi-rpc-client.cjs +39 -22
- package/dist/runtime/pi-rpc-client.mjs +39 -22
- package/dist/runtime/provider-error-classifier.cjs +39 -0
- package/dist/runtime/provider-error-classifier.mjs +34 -0
- package/dist/runtime/runtime-adapters.mjs +176 -27
- package/dist/runtime/runtime-host.cjs +630 -19
- package/dist/runtime/runtime-host.mjs +628 -18
- package/dist/runtime/runtime-readiness.cjs +6 -4
- package/dist/runtime/runtime-readiness.mjs +6 -4
- package/package.json +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PiRpcClient = void 0;
|
|
4
|
-
/** A strict
|
|
4
|
+
/** A strict LF-delimited client for Pi's public RPC protocol. */
|
|
5
5
|
class PiRpcClient {
|
|
6
6
|
process;
|
|
7
7
|
pending = new Map();
|
|
@@ -11,9 +11,10 @@ class PiRpcClient {
|
|
|
11
11
|
inputTimeoutMs;
|
|
12
12
|
inputProgressTimeoutMs;
|
|
13
13
|
inputMaxTimeoutMs;
|
|
14
|
-
maxFrameBytes;
|
|
15
14
|
maxStderrBytes;
|
|
16
15
|
shutdownGraceMs;
|
|
16
|
+
compactTimeoutMs;
|
|
17
|
+
expiredRequestIds = new Set();
|
|
17
18
|
stdoutBuffer = Buffer.alloc(0);
|
|
18
19
|
stderrBuffer = Buffer.alloc(0);
|
|
19
20
|
exited = false;
|
|
@@ -29,9 +30,11 @@ class PiRpcClient {
|
|
|
29
30
|
if (this.requestTimeoutMs <= 0 || this.inputTimeoutMs <= 0 || this.inputProgressTimeoutMs <= 0
|
|
30
31
|
|| this.inputMaxTimeoutMs < this.inputTimeoutMs)
|
|
31
32
|
throw new Error("Pi RPC timeout options are invalid");
|
|
32
|
-
this.maxFrameBytes = options.maxFrameBytes ?? 1024 * 1024;
|
|
33
33
|
this.maxStderrBytes = options.maxStderrBytes ?? 64 * 1024;
|
|
34
34
|
this.shutdownGraceMs = options.shutdownGraceMs ?? 2_000;
|
|
35
|
+
this.compactTimeoutMs = options.compactTimeoutMs ?? 120_000;
|
|
36
|
+
if (this.compactTimeoutMs <= 0)
|
|
37
|
+
throw new Error("Pi compact timeout must be positive");
|
|
35
38
|
process.stdout?.on("data", (chunk) => this.consume(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
36
39
|
process.stdout?.on("end", () => {
|
|
37
40
|
if (this.stdoutBuffer.length)
|
|
@@ -61,19 +64,26 @@ class PiRpcClient {
|
|
|
61
64
|
return () => this.failureListeners.delete(listener);
|
|
62
65
|
}
|
|
63
66
|
request(command, fields = {}) {
|
|
67
|
+
const input = isInputCommand(command);
|
|
68
|
+
return this.requestInternal(command, fields, input ? this.inputTimeoutMs : this.requestTimeoutMs, input ? Date.now() + this.inputMaxTimeoutMs : undefined, true);
|
|
69
|
+
}
|
|
70
|
+
/** Send the documented compact RPC with a Larkin-owned absolute deadline. */
|
|
71
|
+
requestCompact(customInstructions) {
|
|
72
|
+
const startedAt = Date.now();
|
|
73
|
+
return this.requestInternal("compact", customInstructions === undefined ? {} : { customInstructions }, this.compactTimeoutMs, startedAt + this.compactTimeoutMs, false);
|
|
74
|
+
}
|
|
75
|
+
requestInternal(command, fields, timeoutMs, absoluteDeadlineAt, failClientOnTimeout) {
|
|
64
76
|
if (this.failed)
|
|
65
77
|
return Promise.reject(this.failed);
|
|
66
78
|
const id = `larkin-${++this.nextId}`;
|
|
67
79
|
return new Promise((resolve, reject) => {
|
|
68
80
|
const startedAt = Date.now();
|
|
69
|
-
const input = isInputCommand(command);
|
|
70
81
|
const pending = {
|
|
71
|
-
command, startedAt,
|
|
72
|
-
|
|
73
|
-
resolve: resolve, reject,
|
|
82
|
+
command, startedAt, ...(absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt } : {}),
|
|
83
|
+
failClientOnTimeout, resolve: resolve, reject,
|
|
74
84
|
};
|
|
75
85
|
this.pending.set(id, pending);
|
|
76
|
-
this.armTimeout(id, pending,
|
|
86
|
+
this.armTimeout(id, pending, timeoutMs);
|
|
77
87
|
try {
|
|
78
88
|
if (!this.process.stdin || this.process.stdin.destroyed)
|
|
79
89
|
throw new Error("stdin is unavailable");
|
|
@@ -103,15 +113,8 @@ class PiRpcClient {
|
|
|
103
113
|
this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]);
|
|
104
114
|
while (true) {
|
|
105
115
|
const newline = this.stdoutBuffer.indexOf(0x0a);
|
|
106
|
-
if (newline < 0)
|
|
107
|
-
if (this.stdoutBuffer.length > this.maxFrameBytes)
|
|
108
|
-
this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (newline > this.maxFrameBytes) {
|
|
112
|
-
this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
|
|
116
|
+
if (newline < 0)
|
|
113
117
|
return;
|
|
114
|
-
}
|
|
115
118
|
let frame = this.stdoutBuffer.subarray(0, newline);
|
|
116
119
|
this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
|
|
117
120
|
if (frame.at(-1) === 0x0d)
|
|
@@ -140,6 +143,8 @@ class PiRpcClient {
|
|
|
140
143
|
if (message.type === "response" && typeof message.id === "string") {
|
|
141
144
|
const pending = this.pending.get(message.id);
|
|
142
145
|
if (!pending) {
|
|
146
|
+
if (this.expiredRequestIds.delete(message.id))
|
|
147
|
+
return;
|
|
143
148
|
this.protocolFailure(`unexpected response id ${message.id}`);
|
|
144
149
|
return;
|
|
145
150
|
}
|
|
@@ -181,12 +186,24 @@ class PiRpcClient {
|
|
|
181
186
|
return;
|
|
182
187
|
const elapsed = Date.now() - pending.startedAt;
|
|
183
188
|
const absolute = pending.absoluteDeadlineAt !== undefined && Date.now() >= pending.absoluteDeadlineAt;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
this.pending.delete(id);
|
|
190
|
+
this.expiredRequestIds.add(id);
|
|
191
|
+
while (this.expiredRequestIds.size > 256) {
|
|
192
|
+
const oldest = this.expiredRequestIds.values().next().value;
|
|
193
|
+
if (!oldest)
|
|
194
|
+
break;
|
|
195
|
+
this.expiredRequestIds.delete(oldest);
|
|
196
|
+
}
|
|
197
|
+
const error = pending.command === "compact"
|
|
198
|
+
? new Error(`Pi RPC compact timed out at absolute compact deadline ${elapsed}ms`)
|
|
199
|
+
: isInputCommand(pending.command)
|
|
200
|
+
? new Error(absolute
|
|
201
|
+
? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
|
|
202
|
+
: `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
|
|
203
|
+
: new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
|
|
204
|
+
pending.reject(error);
|
|
205
|
+
if (pending.failClientOnTimeout !== false)
|
|
206
|
+
this.fail(error, true);
|
|
190
207
|
}, delay);
|
|
191
208
|
pending.timer.unref?.();
|
|
192
209
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A strict
|
|
1
|
+
/** A strict LF-delimited client for Pi's public RPC protocol. */
|
|
2
2
|
export class PiRpcClient {
|
|
3
3
|
process;
|
|
4
4
|
pending = new Map();
|
|
@@ -8,9 +8,10 @@ export class PiRpcClient {
|
|
|
8
8
|
inputTimeoutMs;
|
|
9
9
|
inputProgressTimeoutMs;
|
|
10
10
|
inputMaxTimeoutMs;
|
|
11
|
-
maxFrameBytes;
|
|
12
11
|
maxStderrBytes;
|
|
13
12
|
shutdownGraceMs;
|
|
13
|
+
compactTimeoutMs;
|
|
14
|
+
expiredRequestIds = new Set();
|
|
14
15
|
stdoutBuffer = Buffer.alloc(0);
|
|
15
16
|
stderrBuffer = Buffer.alloc(0);
|
|
16
17
|
exited = false;
|
|
@@ -26,9 +27,11 @@ export class PiRpcClient {
|
|
|
26
27
|
if (this.requestTimeoutMs <= 0 || this.inputTimeoutMs <= 0 || this.inputProgressTimeoutMs <= 0
|
|
27
28
|
|| this.inputMaxTimeoutMs < this.inputTimeoutMs)
|
|
28
29
|
throw new Error("Pi RPC timeout options are invalid");
|
|
29
|
-
this.maxFrameBytes = options.maxFrameBytes ?? 1024 * 1024;
|
|
30
30
|
this.maxStderrBytes = options.maxStderrBytes ?? 64 * 1024;
|
|
31
31
|
this.shutdownGraceMs = options.shutdownGraceMs ?? 2_000;
|
|
32
|
+
this.compactTimeoutMs = options.compactTimeoutMs ?? 120_000;
|
|
33
|
+
if (this.compactTimeoutMs <= 0)
|
|
34
|
+
throw new Error("Pi compact timeout must be positive");
|
|
32
35
|
process.stdout?.on("data", (chunk) => this.consume(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk))));
|
|
33
36
|
process.stdout?.on("end", () => {
|
|
34
37
|
if (this.stdoutBuffer.length)
|
|
@@ -58,19 +61,26 @@ export class PiRpcClient {
|
|
|
58
61
|
return () => this.failureListeners.delete(listener);
|
|
59
62
|
}
|
|
60
63
|
request(command, fields = {}) {
|
|
64
|
+
const input = isInputCommand(command);
|
|
65
|
+
return this.requestInternal(command, fields, input ? this.inputTimeoutMs : this.requestTimeoutMs, input ? Date.now() + this.inputMaxTimeoutMs : undefined, true);
|
|
66
|
+
}
|
|
67
|
+
/** Send the documented compact RPC with a Larkin-owned absolute deadline. */
|
|
68
|
+
requestCompact(customInstructions) {
|
|
69
|
+
const startedAt = Date.now();
|
|
70
|
+
return this.requestInternal("compact", customInstructions === undefined ? {} : { customInstructions }, this.compactTimeoutMs, startedAt + this.compactTimeoutMs, false);
|
|
71
|
+
}
|
|
72
|
+
requestInternal(command, fields, timeoutMs, absoluteDeadlineAt, failClientOnTimeout) {
|
|
61
73
|
if (this.failed)
|
|
62
74
|
return Promise.reject(this.failed);
|
|
63
75
|
const id = `larkin-${++this.nextId}`;
|
|
64
76
|
return new Promise((resolve, reject) => {
|
|
65
77
|
const startedAt = Date.now();
|
|
66
|
-
const input = isInputCommand(command);
|
|
67
78
|
const pending = {
|
|
68
|
-
command, startedAt,
|
|
69
|
-
|
|
70
|
-
resolve: resolve, reject,
|
|
79
|
+
command, startedAt, ...(absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt } : {}),
|
|
80
|
+
failClientOnTimeout, resolve: resolve, reject,
|
|
71
81
|
};
|
|
72
82
|
this.pending.set(id, pending);
|
|
73
|
-
this.armTimeout(id, pending,
|
|
83
|
+
this.armTimeout(id, pending, timeoutMs);
|
|
74
84
|
try {
|
|
75
85
|
if (!this.process.stdin || this.process.stdin.destroyed)
|
|
76
86
|
throw new Error("stdin is unavailable");
|
|
@@ -100,15 +110,8 @@ export class PiRpcClient {
|
|
|
100
110
|
this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]);
|
|
101
111
|
while (true) {
|
|
102
112
|
const newline = this.stdoutBuffer.indexOf(0x0a);
|
|
103
|
-
if (newline < 0)
|
|
104
|
-
if (this.stdoutBuffer.length > this.maxFrameBytes)
|
|
105
|
-
this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (newline > this.maxFrameBytes) {
|
|
109
|
-
this.protocolFailure(`frame exceeds ${this.maxFrameBytes} bytes`);
|
|
113
|
+
if (newline < 0)
|
|
110
114
|
return;
|
|
111
|
-
}
|
|
112
115
|
let frame = this.stdoutBuffer.subarray(0, newline);
|
|
113
116
|
this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
|
|
114
117
|
if (frame.at(-1) === 0x0d)
|
|
@@ -137,6 +140,8 @@ export class PiRpcClient {
|
|
|
137
140
|
if (message.type === "response" && typeof message.id === "string") {
|
|
138
141
|
const pending = this.pending.get(message.id);
|
|
139
142
|
if (!pending) {
|
|
143
|
+
if (this.expiredRequestIds.delete(message.id))
|
|
144
|
+
return;
|
|
140
145
|
this.protocolFailure(`unexpected response id ${message.id}`);
|
|
141
146
|
return;
|
|
142
147
|
}
|
|
@@ -178,12 +183,24 @@ export class PiRpcClient {
|
|
|
178
183
|
return;
|
|
179
184
|
const elapsed = Date.now() - pending.startedAt;
|
|
180
185
|
const absolute = pending.absoluteDeadlineAt !== undefined && Date.now() >= pending.absoluteDeadlineAt;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
this.pending.delete(id);
|
|
187
|
+
this.expiredRequestIds.add(id);
|
|
188
|
+
while (this.expiredRequestIds.size > 256) {
|
|
189
|
+
const oldest = this.expiredRequestIds.values().next().value;
|
|
190
|
+
if (!oldest)
|
|
191
|
+
break;
|
|
192
|
+
this.expiredRequestIds.delete(oldest);
|
|
193
|
+
}
|
|
194
|
+
const error = pending.command === "compact"
|
|
195
|
+
? new Error(`Pi RPC compact timed out at absolute compact deadline ${elapsed}ms`)
|
|
196
|
+
: isInputCommand(pending.command)
|
|
197
|
+
? new Error(absolute
|
|
198
|
+
? `Pi RPC ${pending.command} preflight timed out at absolute ${this.inputMaxTimeoutMs}ms limit`
|
|
199
|
+
: `Pi RPC ${pending.command} preflight timed out after ${elapsed}ms without progress`)
|
|
200
|
+
: new Error(`Pi RPC ${pending.command} timed out after ${this.requestTimeoutMs}ms`);
|
|
201
|
+
pending.reject(error);
|
|
202
|
+
if (pending.failClientOnTimeout !== false)
|
|
203
|
+
this.fail(error, true);
|
|
187
204
|
}, delay);
|
|
188
205
|
pending.timer.unref?.();
|
|
189
206
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = exports.CODEX_ERROR_PREFIX = void 0;
|
|
4
|
+
exports.buildStrictProviderErrorInput = buildStrictProviderErrorInput;
|
|
5
|
+
exports.classifyStrictProviderError = classifyStrictProviderError;
|
|
6
|
+
exports.CODEX_ERROR_PREFIX = "Codex error: ";
|
|
7
|
+
exports.CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
|
|
8
|
+
exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
|
|
9
|
+
/** Build the one structured provider-error shape shared by ingestion and recovery. */
|
|
10
|
+
function buildStrictProviderErrorInput(source) {
|
|
11
|
+
const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)
|
|
12
|
+
? source.upstream : {};
|
|
13
|
+
return {
|
|
14
|
+
message: source.reason !== undefined ? source.reason : source.message,
|
|
15
|
+
code: source.code ?? upstream.code,
|
|
16
|
+
type: source.type ?? upstream.type,
|
|
17
|
+
errorCategory: source.errorCategory,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The recovery path intentionally recognizes only the complete canonical
|
|
22
|
+
* provider message, or the exact internal projection when its category is
|
|
23
|
+
* persisted alongside it. The Codex wrapper is the only normalization
|
|
24
|
+
* permitted for the provider message.
|
|
25
|
+
*/
|
|
26
|
+
function classifyStrictProviderError(error) {
|
|
27
|
+
if (typeof error.message !== "string")
|
|
28
|
+
return undefined;
|
|
29
|
+
if (error.errorCategory !== undefined && error.errorCategory !== "context_window")
|
|
30
|
+
return undefined;
|
|
31
|
+
const message = error.message.startsWith(exports.CODEX_ERROR_PREFIX)
|
|
32
|
+
? error.message.slice(exports.CODEX_ERROR_PREFIX.length) : error.message;
|
|
33
|
+
if (message === exports.CANONICAL_CONTEXT_WINDOW_MESSAGE)
|
|
34
|
+
return "context_window";
|
|
35
|
+
if (error.errorCategory === "context_window" && error.message === exports.INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON) {
|
|
36
|
+
return "context_window";
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const CODEX_ERROR_PREFIX = "Codex error: ";
|
|
2
|
+
export const CANONICAL_CONTEXT_WINDOW_MESSAGE = "Your input exceeds the context window of this model. Please adjust your input and try again.";
|
|
3
|
+
export const INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON = "provider rejected the input because the context window was exceeded";
|
|
4
|
+
/** Build the one structured provider-error shape shared by ingestion and recovery. */
|
|
5
|
+
export function buildStrictProviderErrorInput(source) {
|
|
6
|
+
const upstream = source.upstream && typeof source.upstream === "object" && !Array.isArray(source.upstream)
|
|
7
|
+
? source.upstream : {};
|
|
8
|
+
return {
|
|
9
|
+
message: source.reason !== undefined ? source.reason : source.message,
|
|
10
|
+
code: source.code ?? upstream.code,
|
|
11
|
+
type: source.type ?? upstream.type,
|
|
12
|
+
errorCategory: source.errorCategory,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The recovery path intentionally recognizes only the complete canonical
|
|
17
|
+
* provider message, or the exact internal projection when its category is
|
|
18
|
+
* persisted alongside it. The Codex wrapper is the only normalization
|
|
19
|
+
* permitted for the provider message.
|
|
20
|
+
*/
|
|
21
|
+
export function classifyStrictProviderError(error) {
|
|
22
|
+
if (typeof error.message !== "string")
|
|
23
|
+
return undefined;
|
|
24
|
+
if (error.errorCategory !== undefined && error.errorCategory !== "context_window")
|
|
25
|
+
return undefined;
|
|
26
|
+
const message = error.message.startsWith(CODEX_ERROR_PREFIX)
|
|
27
|
+
? error.message.slice(CODEX_ERROR_PREFIX.length) : error.message;
|
|
28
|
+
if (message === CANONICAL_CONTEXT_WINDOW_MESSAGE)
|
|
29
|
+
return "context_window";
|
|
30
|
+
if (error.errorCategory === "context_window" && error.message === INTERNAL_CONTEXT_WINDOW_PROJECTION_REASON) {
|
|
31
|
+
return "context_window";
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|