@sailresearch/code 0.1.0 → 0.1.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/README.md +17 -0
- package/package.json +1 -1
- package/src/constants.js +22 -2
- package/src/setup.js +87 -0
package/README.md
CHANGED
|
@@ -56,6 +56,23 @@ Both tiers default to GLM-5.2. `--background-model` moves Claude Code's
|
|
|
56
56
|
haiku/background tier (titles, summaries) to a cheaper model from the
|
|
57
57
|
[Sail catalog](https://docs.sailresearch.com/models).
|
|
58
58
|
|
|
59
|
+
The launcher does **not** force a completion window: Claude Code has no flag
|
|
60
|
+
for adding request metadata, so requests go out without `completion_window` and
|
|
61
|
+
Sail's API default applies. For GLM-5.2 that is the **standard** tier
|
|
62
|
+
(`$0.50` input / `$2.50` output per 1M tokens) because standard pricing is
|
|
63
|
+
available. See [Completion windows](https://docs.sailresearch.com/completion-windows)
|
|
64
|
+
for the latency/price tradeoff and how to opt into another tier.
|
|
65
|
+
|
|
66
|
+
## Commit attribution
|
|
67
|
+
|
|
68
|
+
`setup` sets Claude Code's `attribution.commit` to a Sail co-author trailer
|
|
69
|
+
naming the resolved model (e.g.
|
|
70
|
+
`Co-Authored-By: GLM-5.2 via Sail <noreply@sailresearch.com>` — a `--model`
|
|
71
|
+
override names that model instead) so commits the agent makes are attributed to
|
|
72
|
+
the Sail-served model instead of Anthropic's default trailer; `attribution.pr`
|
|
73
|
+
is emptied. `setup --revert` restores your original `attribution` (from the
|
|
74
|
+
backup) or removes the trailer if the settings file was created by `sail-code`.
|
|
75
|
+
|
|
59
76
|
## Passing flags to claude
|
|
60
77
|
|
|
61
78
|
Everything after `--` goes to `claude` untouched:
|
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -44,6 +44,21 @@ export const DEFAULT_APP_URL = "https://app.sailresearch.com";
|
|
|
44
44
|
*/
|
|
45
45
|
export const DEFAULT_MODEL_CAPABILITIES = "effort,thinking";
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Display metadata for aliases pinned to the DEFAULT (GLM) model. Claude Code
|
|
49
|
+
* otherwise renders the pinned slots with built-in Anthropic-tier names and
|
|
50
|
+
* pricing (e.g. "Custom Opus model", "$5/$25 per Mtok"), which is wrong for a
|
|
51
|
+
* Sail-served GLM row. `_NAME` is the picker label; `_DESCRIPTION` is the
|
|
52
|
+
* per-row subtitle, where we surface Sail's standard-tier pricing for GLM-5.2
|
|
53
|
+
* (see config/models.json: $0.50 input / $2.50 output per 1M tokens). Like
|
|
54
|
+
* `_SUPPORTED_CAPABILITIES`, these are keyed to the ALIAS, so we only set them
|
|
55
|
+
* for aliases pinned to the known default model — a user-overridden --model
|
|
56
|
+
* gets no labels (built-in detection) rather than a mislabel.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_MODEL_NAME = "GLM-5.2 via Sail";
|
|
59
|
+
export const DEFAULT_MODEL_DESCRIPTION =
|
|
60
|
+
"Sail standard pricing: $0.50 input / $2.50 output per 1M tokens";
|
|
61
|
+
|
|
47
62
|
export function buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel }) {
|
|
48
63
|
const base = String(apiUrl ?? "").replace(/\/+$/, "");
|
|
49
64
|
const main = model || DEFAULT_MODEL;
|
|
@@ -63,6 +78,10 @@ export function buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel }) {
|
|
|
63
78
|
if (m === DEFAULT_MODEL) {
|
|
64
79
|
capabilities[`ANTHROPIC_DEFAULT_${alias}_MODEL_SUPPORTED_CAPABILITIES`] =
|
|
65
80
|
DEFAULT_MODEL_CAPABILITIES;
|
|
81
|
+
capabilities[`ANTHROPIC_DEFAULT_${alias}_MODEL_NAME`] =
|
|
82
|
+
DEFAULT_MODEL_NAME;
|
|
83
|
+
capabilities[`ANTHROPIC_DEFAULT_${alias}_MODEL_DESCRIPTION`] =
|
|
84
|
+
DEFAULT_MODEL_DESCRIPTION;
|
|
66
85
|
}
|
|
67
86
|
}
|
|
68
87
|
return {
|
|
@@ -78,8 +97,9 @@ export function buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel }) {
|
|
|
78
97
|
ANTHROPIC_DEFAULT_SONNET_MODEL: main,
|
|
79
98
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: background,
|
|
80
99
|
ANTHROPIC_DEFAULT_FABLE_MODEL: main,
|
|
81
|
-
// Declare effort/thinking
|
|
82
|
-
// (see DEFAULT_MODEL_CAPABILITIES
|
|
100
|
+
// Declare effort/thinking and friendly display metadata for aliases pinned
|
|
101
|
+
// to the default GLM model (see DEFAULT_MODEL_CAPABILITIES / _NAME /
|
|
102
|
+
// _DESCRIPTION). Custom overrides get no declaration.
|
|
83
103
|
...capabilities,
|
|
84
104
|
// Pin subagents to the main Sail model. CLAUDE_CODE_SUBAGENT_MODEL is the
|
|
85
105
|
// highest-priority subagent model source — it outranks a subagent's
|
package/src/setup.js
CHANGED
|
@@ -18,6 +18,7 @@ import path from "node:path";
|
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
CONFLICTING_PROVIDER_VARS,
|
|
21
|
+
DEFAULT_MODEL,
|
|
21
22
|
HEADER_INJECTION_VARS,
|
|
22
23
|
MODEL_ALIAS_COMPANION_VARS,
|
|
23
24
|
OWNED_ENV_KEYS,
|
|
@@ -28,6 +29,54 @@ import { resolveCredentials } from "./run.js";
|
|
|
28
29
|
|
|
29
30
|
export const BACKUP_SUFFIX = ".sail-backup";
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Build the Sail commit-attribution trailer for the resolved main model. Claude
|
|
34
|
+
* Code's top-level `attribution.commit` is the trailer appended to commits/PRs
|
|
35
|
+
* the agent makes; the default is a Claude/model-derived trailer. We replace it
|
|
36
|
+
* with a Sail trailer so commits made through the launcher are attributed to
|
|
37
|
+
* the Sail-served model, not Anthropic. The trailer names the model the session
|
|
38
|
+
* actually uses — for the default GLM model that's the friendly "GLM-5.2"; for
|
|
39
|
+
* a user-selected `--model` we use the model id's basename (e.g.
|
|
40
|
+
* `openai/gpt-oss-120b` → `gpt-oss-120b`) so a non-default setup isn't
|
|
41
|
+
* misattributed to GLM-5.2. `attribution.pr` is the PR-body attribution line;
|
|
42
|
+
* empty disables it (we don't add one).
|
|
43
|
+
*/
|
|
44
|
+
export function sailCommitTrailer(mainModel) {
|
|
45
|
+
const model = mainModel || DEFAULT_MODEL;
|
|
46
|
+
const label =
|
|
47
|
+
model === DEFAULT_MODEL ? "GLM-5.2" : model.split("/").pop() || model;
|
|
48
|
+
return `Co-Authored-By: ${label} via Sail <noreply@sailresearch.com>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** `attribution.pr` value sail-code writes (empty = disable PR attribution). */
|
|
52
|
+
export const SAIL_PR_ATTRIBUTION = "";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Keys of the `attribution` object this tool WRITES in setup. `sessionUrl` is
|
|
56
|
+
* left alone (unset unless we confirm Claude Code appends it in this path — we
|
|
57
|
+
* avoid changing more attribution behavior than requested). Note: revert's
|
|
58
|
+
* no-backup path strips only the distinctive `commit` trailer (see revert()),
|
|
59
|
+
* not `pr` — `pr: ""` is meaningful user config (Claude Code's documented way
|
|
60
|
+
* to disable PR attribution) and indistinguishable from what we write.
|
|
61
|
+
*/
|
|
62
|
+
export const OWNED_ATTRIBUTION_KEYS = ["commit", "pr"];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge the Sail commit attribution into a settings object's top-level
|
|
66
|
+
* `attribution`. `mainModel` is the resolved startup model (the `main` from
|
|
67
|
+
* buildClaudeEnv) so the trailer names the model the session actually uses,
|
|
68
|
+
* including a user-selected `--model`. Preserves any unrelated attribution
|
|
69
|
+
* keys the user set (e.g. sessionUrl). Mutates `settings.attribution`.
|
|
70
|
+
* Exported so tests exercise the real merge, not a copy.
|
|
71
|
+
*/
|
|
72
|
+
export function mergeAttribution(settings, mainModel) {
|
|
73
|
+
settings.attribution = {
|
|
74
|
+
...(settings.attribution ?? {}),
|
|
75
|
+
commit: sailCommitTrailer(mainModel),
|
|
76
|
+
pr: SAIL_PR_ATTRIBUTION,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
31
80
|
/**
|
|
32
81
|
* Backup sentinel recorded when the settings file did NOT exist before the
|
|
33
82
|
* first `setup`. A re-run then finds a backup already present (so it won't
|
|
@@ -338,6 +387,10 @@ export async function setup({
|
|
|
338
387
|
|
|
339
388
|
const { apiKey, apiUrl } = await resolveCredentials({ modeFlag });
|
|
340
389
|
const env = buildClaudeEnv({ apiUrl, apiKey, model, backgroundModel });
|
|
390
|
+
// The commit-attribution trailer names the resolved startup model (mirrors
|
|
391
|
+
// buildClaudeEnv's `main` resolution) so a non-default --model setup isn't
|
|
392
|
+
// misattributed to GLM-5.2.
|
|
393
|
+
const mainModel = model || DEFAULT_MODEL;
|
|
341
394
|
|
|
342
395
|
const { settings, existed } = readSettings(filePath);
|
|
343
396
|
// Create the settings dir before writing the backup/sentinel. On a fresh
|
|
@@ -371,6 +424,7 @@ export async function setup({
|
|
|
371
424
|
}
|
|
372
425
|
|
|
373
426
|
const removedKeys = mergeSailEnv(settings, env, { scope });
|
|
427
|
+
mergeAttribution(settings, mainModel);
|
|
374
428
|
writeSettings(filePath, settings, { containsSecret: true });
|
|
375
429
|
|
|
376
430
|
if (removedKeys.length > 0) {
|
|
@@ -424,6 +478,12 @@ export async function setup({
|
|
|
424
478
|
reach +
|
|
425
479
|
" Undo anytime with: " +
|
|
426
480
|
revertCmd +
|
|
481
|
+
"\n" +
|
|
482
|
+
"Also set `attribution.commit` to a Sail co-author trailer naming the " +
|
|
483
|
+
"resolved model (" +
|
|
484
|
+
sailCommitTrailer(mainModel) +
|
|
485
|
+
") so commits the agent makes are attributed to the Sail-served model " +
|
|
486
|
+
"(--revert restores your original attribution)." +
|
|
427
487
|
"\n\nVS Code note: the extension reads this file for its sessions, but " +
|
|
428
488
|
"its own pre-launch login check does not — if you have no saved " +
|
|
429
489
|
"Anthropic login, also add these to VS Code's user settings " +
|
|
@@ -474,6 +534,33 @@ export function revert({ scope = "user" }) {
|
|
|
474
534
|
}
|
|
475
535
|
if (Object.keys(settings.env).length === 0) delete settings.env;
|
|
476
536
|
}
|
|
537
|
+
// Remove the Sail commit attribution, but only where the value still matches
|
|
538
|
+
// a trailer we can recognize as ours — a value the user replaced after setup
|
|
539
|
+
// is theirs, not ours (mirrors the empty-override logic above). Drop the
|
|
540
|
+
// attribution block if it becomes empty. Unrelated attribution keys (e.g.
|
|
541
|
+
// sessionUrl) are preserved.
|
|
542
|
+
//
|
|
543
|
+
// We match only the DEFAULT-model trailer ("GLM-5.2 via Sail") here: revert
|
|
544
|
+
// doesn't know which model setup used (it has no --model flag and no record of
|
|
545
|
+
// the setup-time choice), so a custom-`--model` trailer can't be reliably
|
|
546
|
+
// reconstructed. Leaving a custom-model trailer in place on this fallback path
|
|
547
|
+
// is the conservative choice (don't clobber something we can't identify as
|
|
548
|
+
// ours); the byte-backup revert path restores the original verbatim, so this
|
|
549
|
+
// only matters when the backup is gone — re-run `setup` then `--revert` to
|
|
550
|
+
// clear a custom-model trailer.
|
|
551
|
+
//
|
|
552
|
+
// `pr` is intentionally NOT stripped on this no-backup path: Claude Code
|
|
553
|
+
// documents `pr: ""` as the normal setting for disabling PR attribution, so an
|
|
554
|
+
// empty `pr` is meaningful user configuration indistinguishable from what we
|
|
555
|
+
// write. The byte-backup revert path restores the original `pr` verbatim.
|
|
556
|
+
if (settings.attribution && typeof settings.attribution === "object") {
|
|
557
|
+
if (settings.attribution.commit === sailCommitTrailer(DEFAULT_MODEL)) {
|
|
558
|
+
delete settings.attribution.commit;
|
|
559
|
+
}
|
|
560
|
+
if (Object.keys(settings.attribution).length === 0) {
|
|
561
|
+
delete settings.attribution;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
477
564
|
writeSettings(filePath, settings);
|
|
478
565
|
console.error(
|
|
479
566
|
`Removed Sail routing keys from ${filePath} (no backup was present).`,
|