cartethyia 2.6.2 → 2.6.3
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/dist/dashboard/assets/{BannedPage-BMFQM5pm.js → BannedPage-C8pfspAj.js} +1 -1
- package/dist/dashboard/assets/BannedPage-C8pfspAj.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BP-8qZqc.js → CliToolDetailPage-D-OwfzDV.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-D-OwfzDV.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DpJeG_Op.js → CliToolsPage-DqRcDO5j.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DqRcDO5j.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-jNI-D44o.js → CombosPage-B-a0l8yE.js} +1 -1
- package/dist/dashboard/assets/CombosPage-B-a0l8yE.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CQpDMg8K.js → ConsoleLogPage-BVVGUdqt.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVVGUdqt.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-DKU4KjlR.js → CustomizationPage-BzmbE05k.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BzmbE05k.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-DUKqhSoT.js → HealthEventsModal-BRDc_wx8.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-BRDc_wx8.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DaUYRu4n.js → LoginPage-BdSGc5J0.js} +1 -1
- package/dist/dashboard/assets/LoginPage-BdSGc5J0.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-Cvo6sK7M.js → OverviewPage-D3fMRkpP.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-D3fMRkpP.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderDetailPage-BrL6Yl9w.js → ProviderDetailPage-DlBwPsHM.js} +1 -1
- package/dist/dashboard/assets/ProviderDetailPage-DlBwPsHM.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-CIo_CRe_.js → ProvidersPage-T7WamMuq.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-T7WamMuq.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BrsHDiRx.js → ProxyPage-CKJb_Kj4.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-CKJb_Kj4.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-DkUwJFpM.js → QuotaPage-hxJ1hGES.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-hxJ1hGES.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BXK4gIFl.js → SettingsPage-CpEErAq5.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CpEErAq5.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-BiIPnecp.js → SetupPage-DRsoneXm.js} +1 -1
- package/dist/dashboard/assets/SetupPage-DRsoneXm.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-CclP89S9.js → SortableList-QvDz0eQo.js} +1 -1
- package/dist/dashboard/assets/SortableList-QvDz0eQo.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-Djt-Mk-N.js → StudioPage-6yLBksbv.js} +1 -1
- package/dist/dashboard/assets/StudioPage-6yLBksbv.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-DaGf9BI1.js → UsagePage-CpB88NH4.js} +1 -1
- package/dist/dashboard/assets/UsagePage-CpB88NH4.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BY4QChsA.js → entry-8U_8IAW2.js} +3 -3
- package/dist/dashboard/assets/entry-8U_8IAW2.js.gz +0 -0
- package/dist/dashboard/assets/{index-isGWt18Z.js → index-BG48fsZ2.js} +2 -2
- package/dist/dashboard/assets/index-BG48fsZ2.js.gz +0 -0
- package/dist/dashboard/assets/{select-Dpp8tE7Z.js → select-AnGKpdl1.js} +1 -1
- package/dist/dashboard/assets/select-AnGKpdl1.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +21 -21
- package/package.json +96 -96
- package/src/console/backup/store.ts +10 -2
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +105 -0
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +24 -20
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -2
- package/src/providers/integrations/buddy/codebuddy/provider.ts +15 -15
- package/src/providers/integrations/buddy/codebuddy/shared.ts +7 -5
- package/src/providers/integrations/buddy/workbuddy/provider.ts +13 -12
- package/src/providers/integrations/buddy/workbuddy/shared.ts +14 -4
- package/dist/dashboard/assets/BannedPage-BMFQM5pm.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-BP-8qZqc.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DpJeG_Op.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-jNI-D44o.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CQpDMg8K.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DKU4KjlR.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-DUKqhSoT.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DaUYRu4n.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Cvo6sK7M.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BrL6Yl9w.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CIo_CRe_.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BrsHDiRx.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-DkUwJFpM.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BXK4gIFl.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-BiIPnecp.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-CclP89S9.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-Djt-Mk-N.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DaGf9BI1.js.gz +0 -0
- package/dist/dashboard/assets/entry-BY4QChsA.js.gz +0 -0
- package/dist/dashboard/assets/index-isGWt18Z.js.gz +0 -0
- package/dist/dashboard/assets/select-Dpp8tE7Z.js.gz +0 -0
|
@@ -559,6 +559,7 @@ async function insertRows(
|
|
|
559
559
|
readonly keyOf?: (row: BackupRow) => string;
|
|
560
560
|
readonly conflictTarget?: readonly Column[];
|
|
561
561
|
readonly ignoreConflicts?: boolean;
|
|
562
|
+
readonly tenantId?: string;
|
|
562
563
|
} = {},
|
|
563
564
|
): Promise<number> {
|
|
564
565
|
// Normalise rows up front: filter skips, drop empty-value rows (no
|
|
@@ -570,6 +571,7 @@ async function insertRows(
|
|
|
570
571
|
continue;
|
|
571
572
|
}
|
|
572
573
|
const values = toValues(table, row);
|
|
574
|
+
if (options.tenantId !== undefined && "tenantId" in values) values.tenantId = options.tenantId;
|
|
573
575
|
if (Object.keys(values).length === 0) continue;
|
|
574
576
|
prepared.push(values);
|
|
575
577
|
}
|
|
@@ -828,6 +830,7 @@ export async function applyRestore(
|
|
|
828
830
|
: entry.rows;
|
|
829
831
|
restored[tableName(table)] = await insertRows(scoped, table, rows, {
|
|
830
832
|
...(ownership.kind === "authored" ? { conflictTarget: ownership.keyColumns } : {}),
|
|
833
|
+
tenantId,
|
|
831
834
|
});
|
|
832
835
|
}
|
|
833
836
|
|
|
@@ -837,8 +840,12 @@ export async function applyRestore(
|
|
|
837
840
|
const entry = validation.tables.get(table);
|
|
838
841
|
if (entry === undefined) continue;
|
|
839
842
|
if (table === telemetryEvents) {
|
|
843
|
+
// The payload rows are remapped to the restoring tenant before the
|
|
844
|
+
// idempotency filter runs, so the dedupe keys and the appended file
|
|
845
|
+
// rows are owned by the receiving tenant, not the backup's tenant.
|
|
846
|
+
const remapped = entry.rows.map((row) => ({ ...row, tenant_id: tenantId }));
|
|
840
847
|
const existing = await existingEventFileKeys(db, tenantId, entry.rows);
|
|
841
|
-
const fresh =
|
|
848
|
+
const fresh = remapped.filter((row) => !existing.has(telemetryKey(row)));
|
|
842
849
|
await appendRestoredEvents(fresh);
|
|
843
850
|
restored[tableName(table)] = fresh.length;
|
|
844
851
|
skipped[tableName(table)] = entry.rows.length - fresh.length;
|
|
@@ -850,11 +857,12 @@ export async function applyRestore(
|
|
|
850
857
|
telemetryUsageTotals.entityId,
|
|
851
858
|
],
|
|
852
859
|
ignoreConflicts: true,
|
|
860
|
+
tenantId,
|
|
853
861
|
});
|
|
854
862
|
restored[tableName(table)] = restoredCount;
|
|
855
863
|
skipped[tableName(table)] = entry.rows.length - restoredCount;
|
|
856
864
|
} else {
|
|
857
|
-
restored[tableName(table)] = await insertRows(scoped, table, entry.rows);
|
|
865
|
+
restored[tableName(table)] = await insertRows(scoped, table, entry.rows, { tenantId });
|
|
858
866
|
}
|
|
859
867
|
}
|
|
860
868
|
});
|
|
@@ -308,6 +308,107 @@ function rewriteBlockedCodeField(message: Record<string, unknown>, field: string
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
const BUDDY_ZWSP = "\u200b";
|
|
312
|
+
|
|
313
|
+
function escapeBuddyRegexLiteral(value: string): string {
|
|
314
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Builds a matcher that tolerates the zero-width spaces upstream interleaves
|
|
319
|
+
* between characters, so an identity template is still recognized after a
|
|
320
|
+
* normalizer has split it apart.
|
|
321
|
+
*/
|
|
322
|
+
function buddyTemplateSource(text: string): string {
|
|
323
|
+
const words = text
|
|
324
|
+
.split(" ")
|
|
325
|
+
.map((word) => [...word].map(escapeBuddyRegexLiteral).join(`${BUDDY_ZWSP}*`));
|
|
326
|
+
return words.join(`[\\s${BUDDY_ZWSP}]+`) + `${BUDDY_ZWSP}*`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function buddyTemplatePattern(text: string): RegExp {
|
|
330
|
+
return new RegExp(`(?<!\\w)${buddyTemplateSource(text)}(?!\\w)`, "i");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// DeepSeek Harness (DSH) role identities. DSH is the harness behind WorkBuddy;
|
|
334
|
+
// a history that still carries its self-description reads to the upstream as a
|
|
335
|
+
// foreign channel, so the hardened client rewrites it back to the WorkBuddy
|
|
336
|
+
// identity rather than forwarding a competing one.
|
|
337
|
+
const DSH_CODING_PATTERN = new RegExp(
|
|
338
|
+
`(?<!\\w)${buddyTemplateSource("You are a coding agent powered by the")}` +
|
|
339
|
+
`[\\s${BUDDY_ZWSP}]+([^\\r\\n]{1,200}?)[\\s${BUDDY_ZWSP}]+` +
|
|
340
|
+
`${buddyTemplateSource("model")}(?!\\w)`,
|
|
341
|
+
"i",
|
|
342
|
+
);
|
|
343
|
+
const DSH_CHANNEL_IDENTITY_PATTERN = buddyTemplatePattern(
|
|
344
|
+
"You are an AI agent powered by DeepSeek Harness",
|
|
345
|
+
);
|
|
346
|
+
const DSH_MINIMAL_PATTERN = buddyTemplatePattern("You are a helpful software engineer assistant");
|
|
347
|
+
const DSH_DIRECT_PATTERN = buddyTemplatePattern("You are DeepSeek Harness");
|
|
348
|
+
const DSH_WEB_GUI_PATTERN = buddyTemplatePattern("DeepSeek Harness Web GUI");
|
|
349
|
+
|
|
350
|
+
/** The identity DSH role prompts are rewritten to; matches the donor gateway. */
|
|
351
|
+
const DSH_WORKBUDDY_IDENTITY = "You are WorkBuddy, a powerful AI assistant";
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Rewrites DeepSeek Harness role identities in one message's text back to the
|
|
355
|
+
* WorkBuddy identity. Port of the donor gateway's always-on
|
|
356
|
+
* `desensitize_channel_body`, scoped to text the wire actually carries.
|
|
357
|
+
*
|
|
358
|
+
* This normalizes a replayed history (or a surviving system turn) that still
|
|
359
|
+
* claims the DSH harness, so a WorkBuddy request never advertises a competing
|
|
360
|
+
* channel identity upstream. The coding persona keeps the model it named:
|
|
361
|
+
* "You are a coding agent powered by the X model" becomes the WorkBuddy
|
|
362
|
+
* identity plus "powered by the X model".
|
|
363
|
+
*/
|
|
364
|
+
export function scrubBuddyChannelIdentityText(text: string): string {
|
|
365
|
+
if (!text) return text;
|
|
366
|
+
const withCodingPersona = text.replace(
|
|
367
|
+
DSH_CODING_PATTERN,
|
|
368
|
+
(_match, model: string) =>
|
|
369
|
+
`${DSH_WORKBUDDY_IDENTITY}. This conversation is powered by the ${model.trim()} model`,
|
|
370
|
+
);
|
|
371
|
+
return withCodingPersona
|
|
372
|
+
.replace(DSH_CHANNEL_IDENTITY_PATTERN, DSH_WORKBUDDY_IDENTITY)
|
|
373
|
+
.replace(DSH_MINIMAL_PATTERN, DSH_WORKBUDDY_IDENTITY)
|
|
374
|
+
.replace(DSH_DIRECT_PATTERN, DSH_WORKBUDDY_IDENTITY)
|
|
375
|
+
.replace(DSH_WEB_GUI_PATTERN, "WorkBuddy Web GUI");
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function scrubBuddyMessageText(message: Record<string, unknown>, field: string): void {
|
|
379
|
+
const value = message[field];
|
|
380
|
+
if (typeof value === "string") {
|
|
381
|
+
const scrubbed = scrubBuddyChannelIdentityText(value);
|
|
382
|
+
if (scrubbed !== value) message[field] = scrubbed;
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!Array.isArray(value)) return;
|
|
386
|
+
for (const part of value) {
|
|
387
|
+
if (!isRecord(part)) continue;
|
|
388
|
+
const text = part["text"];
|
|
389
|
+
if (typeof text === "string") {
|
|
390
|
+
const scrubbed = scrubBuddyChannelIdentityText(text);
|
|
391
|
+
if (scrubbed !== text) part["text"] = scrubbed;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Applies the DSH identity scrub across a buddy conversation: every
|
|
398
|
+
* `system`/`developer`/`assistant` turn's text and `reasoning_content` is
|
|
399
|
+
* normalized, so a replayed harness history cannot carry a competing channel
|
|
400
|
+
* identity upstream.
|
|
401
|
+
*/
|
|
402
|
+
export function scrubBuddyChannelIdentity(messages: Array<Record<string, unknown>>): void {
|
|
403
|
+
for (const message of messages) {
|
|
404
|
+
if (!isRecord(message)) continue;
|
|
405
|
+
const role = message["role"];
|
|
406
|
+
if (role !== "system" && role !== "developer" && role !== "assistant") continue;
|
|
407
|
+
scrubBuddyMessageText(message, "content");
|
|
408
|
+
scrubBuddyMessageText(message, "reasoning_content");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
311
412
|
/**
|
|
312
413
|
* Payload prefix shared by the buddy pre-payload hooks: force `stream=true`,
|
|
313
414
|
* apply the provider's DeepSeek thinking contract, opt `reasoning_summary` in
|
|
@@ -370,6 +471,10 @@ export function buddyPrePayloadCommon(
|
|
|
370
471
|
repackBuddyToolResultBlocks(arr);
|
|
371
472
|
cleanupBuddyOrphanToolPairs(arr);
|
|
372
473
|
coalesceConsecutiveAssistantMessages(arr);
|
|
474
|
+
// Normalize a replayed harness history before the blocked-code rewrite
|
|
475
|
+
// below: DSH role identities are replaced with the WorkBuddy identity, so
|
|
476
|
+
// a WorkBuddy-family request never forwards a competing channel identity.
|
|
477
|
+
scrubBuddyChannelIdentity(arr);
|
|
373
478
|
// After every merge: a quoted `11128` in any surviving text blocks the
|
|
374
479
|
// whole request, and the merges above are what produce the final text.
|
|
375
480
|
rewriteBuddyBlockedCode(payload);
|
|
@@ -2,43 +2,47 @@
|
|
|
2
2
|
* The header family only the CLI identity sends.
|
|
3
3
|
*
|
|
4
4
|
* Everything here comes from the two reference gateways, which agree: the
|
|
5
|
-
* desktop product sends conversation/tracing headers and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* desktop product sends conversation/tracing headers and a device token, while
|
|
6
|
+
* the CLI sends the agent envelope plus the OpenAI Node SDK fingerprint. The
|
|
7
|
+
* CLI still drops the browser *transport* headers (`origin`, `referer`,
|
|
8
|
+
* `accept-language`) and the device token, but keeps the request-shape markers
|
|
9
|
+
* (`x-requested-with`, `x-codebuddy-request`) the reference sends on every call
|
|
10
|
+
* regardless of transport — see the CLI branches in each provider's
|
|
11
|
+
* `*-shared.ts`.
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* The SDK fingerprint is pinned, not host-derived. Both references pin a fixed
|
|
14
|
+
* set (codebuddy2api pins Linux/x64/v24.21.0); this gateway pins the macOS set
|
|
15
|
+
* instead, so every CLI dispatch presents one consistent client fingerprint no
|
|
16
|
+
* matter which machine the gateway runs on. A host-derived fingerprint would
|
|
17
|
+
* advertise whatever OS the process happens to run under — a value no upstream
|
|
18
|
+
* risk model has seen for this client brand, and an easy outlier to key on.
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
/** OpenAI Node SDK 6.25.0 `x-stainless-*` fingerprint; both references agree. */
|
|
18
22
|
const STAINLESS_PACKAGE_VERSION = "6.25.0";
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* The pinned Node runtime the fingerprint claims. Held constant across
|
|
26
|
+
* dispatches: a drifting `process.version` would advertise a different Node
|
|
27
|
+
* build per deployment, which is not what one installed client looks like.
|
|
28
|
+
*/
|
|
29
|
+
const STAINLESS_RUNTIME_VERSION = "v24.21.0";
|
|
27
30
|
|
|
28
31
|
/**
|
|
29
32
|
* The SDK fingerprint headers.
|
|
30
33
|
*
|
|
31
34
|
* `x-stainless-timeout` is in the hub only and `x-stainless-retry-count` is in
|
|
32
|
-
* both; the SDK sends both, so both go out.
|
|
35
|
+
* both; the SDK sends both, so both go out. The OS/arch pair is the pinned
|
|
36
|
+
* macOS fingerprint rather than this process's host.
|
|
33
37
|
*/
|
|
34
38
|
export function buddyStainlessHeaders(): Record<string, string> {
|
|
35
39
|
return {
|
|
36
40
|
"x-stainless-lang": "js",
|
|
37
41
|
"x-stainless-package-version": STAINLESS_PACKAGE_VERSION,
|
|
38
|
-
"x-stainless-os":
|
|
39
|
-
"x-stainless-arch":
|
|
42
|
+
"x-stainless-os": "MacOS",
|
|
43
|
+
"x-stainless-arch": "x64",
|
|
40
44
|
"x-stainless-runtime": "node",
|
|
41
|
-
"x-stainless-runtime-version":
|
|
45
|
+
"x-stainless-runtime-version": STAINLESS_RUNTIME_VERSION,
|
|
42
46
|
"x-stainless-retry-count": "0",
|
|
43
47
|
"x-stainless-timeout": "1200",
|
|
44
48
|
};
|
|
@@ -34,6 +34,11 @@ function newMessageId(): string {
|
|
|
34
34
|
return crypto.randomUUID().replaceAll("-", "");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Renders a 32-char hex id as a dashed UUID (`8-4-4-4-12`). */
|
|
38
|
+
function toUuidHex(hex: string): string {
|
|
39
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
/** True when `value` is a valid B3 trace id: 16 or 32 hex characters. */
|
|
38
43
|
function isValidTraceId(value: string): boolean {
|
|
39
44
|
if (value.length !== 16 && value.length !== 32) return false;
|
|
@@ -53,6 +58,18 @@ export interface BuddyConversationIds {
|
|
|
53
58
|
readonly conversationRequestId?: string | undefined;
|
|
54
59
|
/** Inbound trace id to pass through; falls back to the conversation request id. */
|
|
55
60
|
readonly traceId?: string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Wire format for the two message-scoped ids (`x-conversation-message-id`,
|
|
63
|
+
* `x-request-id`).
|
|
64
|
+
*
|
|
65
|
+
* The reference emits these as dashed UUIDs on the WorkBuddy desktop product
|
|
66
|
+
* and as 32-hex on CLI traffic (`build_attempt_headers` converts only the
|
|
67
|
+
* message/request ids, and only for the WorkBuddy profiles). The turn
|
|
68
|
+
* aggregation key (`x-conversation-request-id` / `x-root-request-id`) and the
|
|
69
|
+
* B3/W3C trace ids stay hex in both styles, because B3's spec accepts only
|
|
70
|
+
* hex. Default `"hex"`.
|
|
71
|
+
*/
|
|
72
|
+
readonly messageIdStyle?: "hex" | "uuid" | undefined;
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
/**
|
|
@@ -71,14 +88,17 @@ export function buddyConversationHeaders(
|
|
|
71
88
|
? ids.conversationRequestId.trim()
|
|
72
89
|
: newMessageId();
|
|
73
90
|
const messageId = newMessageId();
|
|
91
|
+
// The message-scoped ids may render as a dashed UUID (WorkBuddy desktop) or
|
|
92
|
+
// as 32-hex (CLI); the raw hex is still what the B3/W3C trace ids derive from.
|
|
93
|
+
const messageHeaderValue = ids.messageIdStyle === "uuid" ? toUuidHex(messageId) : messageId;
|
|
74
94
|
const traceId =
|
|
75
95
|
ids.traceId && ids.traceId.trim().length > 0 ? ids.traceId.trim() : conversationRequestId;
|
|
76
96
|
const b3TraceId = isValidTraceId(conversationRequestId) ? conversationRequestId : messageId;
|
|
77
97
|
|
|
78
98
|
const headers: Record<string, string> = {
|
|
79
99
|
"x-conversation-request-id": conversationRequestId,
|
|
80
|
-
"x-conversation-message-id":
|
|
81
|
-
"x-request-id":
|
|
100
|
+
"x-conversation-message-id": messageHeaderValue,
|
|
101
|
+
"x-request-id": messageHeaderValue,
|
|
82
102
|
"x-root-request-id": conversationRequestId,
|
|
83
103
|
"x-trace-id": traceId,
|
|
84
104
|
// W3C trace context alongside the B3 set below: the upstream reads both,
|
|
@@ -48,24 +48,24 @@ export const CODEBUDDY_BASE_URL = providerBaseUrl("cb");
|
|
|
48
48
|
/**
|
|
49
49
|
* Leading system turn for this variant.
|
|
50
50
|
*
|
|
51
|
-
* The
|
|
52
|
-
* `11128`)
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* system prompt the caller actually sent.
|
|
51
|
+
* The wire contract is that the request *opens* with a `system` turn (code
|
|
52
|
+
* `11128`); the gateway replaces any caller `system`/`developer` turn with this
|
|
53
|
+
* fixed prompt. The text is the CodeBuddy CLI's own leading prompt — the same
|
|
54
|
+
* one the reference client (and the donor gateway's `prepend` prompt mode)
|
|
55
|
+
* sends — so a proxied request is byte-faithful to an official client rather
|
|
56
|
+
* than a neutral stand-in.
|
|
58
57
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* Keeping the official identity is forward-compatible: the upstream is not
|
|
59
|
+
* observed to validate this text today, but a proxy that claims the real
|
|
60
|
+
* client's identity cannot be caught out if it starts to. A previous neutral
|
|
61
|
+
* persona ("You are a pragmatic and direct software engineering assistant…")
|
|
62
|
+
* worked, but diverged from every request the official CLI emits.
|
|
62
63
|
*/
|
|
63
64
|
export const CODEBUDDY_SYSTEM_PROMPT =
|
|
64
|
-
"You are
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"instead of guessing silently.";
|
|
65
|
+
"You are CodeBuddy Code.\n\n" +
|
|
66
|
+
"You are an interactive CLI tool that helps users with software engineering tasks. " +
|
|
67
|
+
"Use the instructions below and the tools available to you to assist the user. " +
|
|
68
|
+
"Your main goal is to follow the USER's instructions at each message, denoted by the <user_query> tag.";
|
|
69
69
|
|
|
70
70
|
function codeBuddyIntlPrePayload(
|
|
71
71
|
payload: Record<string, unknown>,
|
|
@@ -41,11 +41,11 @@ export async function codebuddyHeaders(
|
|
|
41
41
|
});
|
|
42
42
|
if (resolveBuddyIdentity(context, cliIdentity) === "cli") {
|
|
43
43
|
return {
|
|
44
|
-
// The CLI sends `Accept: application/json` and no device token.
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
44
|
+
// The CLI sends `Accept: application/json` and no device token. It keeps
|
|
45
|
+
// the request-shape markers (`X-Requested-With`, `X-CodeBuddy-Request`)
|
|
46
|
+
// the reference sends on every call regardless of transport, but drops
|
|
47
|
+
// the browser *transport* header (`accept: text/event-stream`) — so the
|
|
48
|
+
// identity is still CLI, not the desktop hybrid.
|
|
49
49
|
accept: "application/json",
|
|
50
50
|
"User-Agent": `CLI/${getCodeBuddyVersion()} CodeBuddy/${getCodeBuddyVersion()}`,
|
|
51
51
|
"X-Product": "SaaS",
|
|
@@ -53,6 +53,8 @@ export async function codebuddyHeaders(
|
|
|
53
53
|
"X-IDE-Name": "CLI",
|
|
54
54
|
"X-IDE-Version": getCodeBuddyVersion(),
|
|
55
55
|
"X-Domain": codebuddyDomain(variant),
|
|
56
|
+
"x-requested-with": "XMLHttpRequest",
|
|
57
|
+
"x-codebuddy-request": "1",
|
|
56
58
|
// The conversation purpose the reference client states. Not required —
|
|
57
59
|
// verified against the live API that a request succeeds without it — but
|
|
58
60
|
// it is part of the client's real request, and matching the client is the
|
|
@@ -49,20 +49,21 @@ export const WORKBUDDY_BASE_URL = providerBaseUrl("workbuddy");
|
|
|
49
49
|
/**
|
|
50
50
|
* Leading system turn for this variant.
|
|
51
51
|
*
|
|
52
|
-
* The upstream
|
|
53
|
-
* the
|
|
54
|
-
* `400 · 11128 "Illegal API invocation from an unapproved
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
52
|
+
* The upstream validates that the wire opens with a `system` turn and treats
|
|
53
|
+
* that prompt as the calling channel's identity: forwarding caller text in its
|
|
54
|
+
* place answers `400 · 11128 "Illegal API invocation from an unapproved
|
|
55
|
+
* channel"`. So this prompt is a wire contract, not merely a product choice —
|
|
56
|
+
* see the sibling in `codebuddy.ts`.
|
|
57
|
+
*
|
|
58
|
+
* The text is WorkBuddy's own leading prompt, matching what the official client
|
|
59
|
+
* (and the donor gateway's `prepend` prompt mode) sends, so a proxied request
|
|
60
|
+
* carries the real client's identity instead of a neutral stand-in. A previous
|
|
61
|
+
* neutral persona was accepted but diverged from every request the official
|
|
62
|
+
* desktop client emits.
|
|
59
63
|
*/
|
|
60
64
|
export const WORKBUDDY_SYSTEM_PROMPT =
|
|
61
|
-
"You are a
|
|
62
|
-
"
|
|
63
|
-
"or do not know, and never claim to have done something you have not done. " +
|
|
64
|
-
"Prefer concrete answers over filler, and say so when a request is ambiguous " +
|
|
65
|
-
"instead of guessing silently.";
|
|
65
|
+
"You are WorkBuddy, a powerful AI assistant.\n\n" +
|
|
66
|
+
"Your main goal is to follow the USER's instructions at each message, denoted by the <user_query> tag.";
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Reconstructs generic function declarations when a later tool-history turn
|
|
@@ -69,19 +69,29 @@ export async function workbuddyHeaders(
|
|
|
69
69
|
clientVersion = getWorkBuddyClientVersion(),
|
|
70
70
|
cliIdentity?: boolean,
|
|
71
71
|
): Promise<Record<string, string>> {
|
|
72
|
+
const identity = resolveBuddyIdentity(context, cliIdentity);
|
|
72
73
|
const conversation = buddyConversationHeaders({
|
|
73
74
|
...(resolveInboundSessionId(context, request) === undefined
|
|
74
75
|
? {}
|
|
75
76
|
: { conversationId: resolveInboundSessionId(context, request) }),
|
|
77
|
+
// The reference emits dashed-UUID message/request ids on the WorkBuddy
|
|
78
|
+
// desktop product and 32-hex on CLI traffic (build_attempt_headers converts
|
|
79
|
+
// only the message/request ids, and only for the WorkBuddy profiles).
|
|
80
|
+
messageIdStyle: identity === "desktop" ? "uuid" : "hex",
|
|
76
81
|
});
|
|
77
82
|
const accountId = context?.credential.account_id;
|
|
78
|
-
if (
|
|
79
|
-
// The reference CLI: issued by the OpenAI Node SDK, so no browser
|
|
80
|
-
// and no device token,
|
|
81
|
-
// exist — only `X-User-Id`,
|
|
83
|
+
if (identity === "cli") {
|
|
84
|
+
// The reference CLI: issued by the OpenAI Node SDK, so no browser
|
|
85
|
+
// *transport* header (origin/referer/accept-language) and no device token,
|
|
86
|
+
// and the machine/session device pair does not exist — only `X-User-Id`,
|
|
87
|
+
// which is `anonymous` without a uid. It keeps the request-shape markers
|
|
88
|
+
// (`x-requested-with`, `x-codebuddy-request`) the reference sends on every
|
|
89
|
+
// call regardless of transport.
|
|
82
90
|
return {
|
|
83
91
|
accept: "application/json",
|
|
84
92
|
"user-agent": `CLI/${getWorkBuddyCliVersion()} CodeBuddy/${getWorkBuddyCliVersion()}`,
|
|
93
|
+
"x-requested-with": "XMLHttpRequest",
|
|
94
|
+
"x-codebuddy-request": "1",
|
|
85
95
|
"x-domain": domain,
|
|
86
96
|
"x-agent-purpose": "conversation",
|
|
87
97
|
"x-ide-name": "CLI",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|