sf snapshot: uncommitted changes after 268m inactivity
This commit is contained in:
parent
7e1631618a
commit
def1edefa9
21 changed files with 306 additions and 23 deletions
|
|
@ -1,2 +1,2 @@
|
||||||
[tools]
|
[tools]
|
||||||
node = "26.1.0"
|
node = "26"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
|
|
|
||||||
66
packages/google-gemini-cli-provider/src/index.d.ts
vendored
Normal file
66
packages/google-gemini-cli-provider/src/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { type ContentGenerator } from "@google/gemini-cli-core/dist/src/core/contentGenerator.js";
|
||||||
|
export interface GeminiCliContentGeneratorOptions {
|
||||||
|
modelId: string;
|
||||||
|
cwd?: string;
|
||||||
|
targetDir?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a Gemini CLI Core content generator for a model.
|
||||||
|
*
|
||||||
|
* Purpose: centralize the Code Assist setup and OAuth bootstrap logic in a
|
||||||
|
* reusable package so SF's Gemini provider can stay focused on stream shaping.
|
||||||
|
*
|
||||||
|
* Consumer: the Google Gemini provider in pi-ai.
|
||||||
|
*/
|
||||||
|
export declare function createGeminiCliContentGenerator(options: GeminiCliContentGeneratorOptions): Promise<ContentGenerator>;
|
||||||
|
/**
|
||||||
|
* Per-model quota bucket from CodeAssistServer.retrieveUserQuota.
|
||||||
|
*/
|
||||||
|
export interface GeminiQuotaBucket {
|
||||||
|
modelId: string;
|
||||||
|
usedFraction: number;
|
||||||
|
remainingFraction: number;
|
||||||
|
resetTime?: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Snapshot of the active gemini-cli account: tier identity, project, and the
|
||||||
|
* full per-model quota table.
|
||||||
|
*
|
||||||
|
* Why a single struct: every consumer (model picker, usage UI, capacity
|
||||||
|
* diagnostics, catalog cache) needs the same three pieces of data. Returning
|
||||||
|
* them together avoids three separate OAuth round trips.
|
||||||
|
*/
|
||||||
|
export interface GeminiAccountSnapshot {
|
||||||
|
projectId: string;
|
||||||
|
/** Active tier id from setupUser.userTier (e.g. "free-tier", "standard-tier"). */
|
||||||
|
userTierId?: string;
|
||||||
|
/** Active tier human label from setupUser.userTierName. */
|
||||||
|
userTierName?: string;
|
||||||
|
/**
|
||||||
|
* Paid tier descriptor when the account has one (e.g. AI Ultra). Carries
|
||||||
|
* id like "g1-ultra-tier" and the marketing name. Distinct from the
|
||||||
|
* effective userTier — a free-tier session can still have a paidTier
|
||||||
|
* marker if the underlying account is subscribed.
|
||||||
|
*/
|
||||||
|
paidTier?: {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
models: GeminiQuotaBucket[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Discover the active gemini-cli account: tier, project, and every model the
|
||||||
|
* account has access to (with per-model usage fraction and reset time).
|
||||||
|
*
|
||||||
|
* Best-effort: any failure (OAuth expired, no project, network) returns null
|
||||||
|
* silently so callers can downgrade gracefully.
|
||||||
|
*
|
||||||
|
* Consumer: SF-side background catalog cache, usage UI, capacity diagnostics.
|
||||||
|
*/
|
||||||
|
export declare function snapshotGeminiCliAccount(cwd?: string): Promise<GeminiAccountSnapshot | null>;
|
||||||
|
/**
|
||||||
|
* Convenience wrapper: just the model IDs the active gemini-cli account has
|
||||||
|
* access to. Returns null on failure (same contract as snapshotGeminiCliAccount).
|
||||||
|
*/
|
||||||
|
export declare function discoverGeminiCliModels(cwd?: string): Promise<string[] | null>;
|
||||||
|
//# sourceMappingURL=index.d.ts.map
|
||||||
1
packages/google-gemini-cli-provider/src/index.d.ts.map
Normal file
1
packages/google-gemini-cli-provider/src/index.d.ts.map
Normal file
File diff suppressed because one or more lines are too long
99
packages/google-gemini-cli-provider/src/index.js
Normal file
99
packages/google-gemini-cli-provider/src/index.js
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* Google Gemini CLI transport helper.
|
||||||
|
*
|
||||||
|
* Purpose: keep the Gemini CLI Core auth and content-generator wiring in a
|
||||||
|
* dedicated workspace package so provider code can depend on one small helper
|
||||||
|
* instead of embedding the upstream integration inline.
|
||||||
|
*
|
||||||
|
* Consumer: `@singularity-forge/ai` Google Gemini provider, plus SF-side
|
||||||
|
* background catalog discovery.
|
||||||
|
*/
|
||||||
|
import { AuthType, CodeAssistServer, getOauthClient, makeFakeConfig, setupUser, } from "@google/gemini-cli-core";
|
||||||
|
import { createContentGenerator, createContentGeneratorConfig, } from "@google/gemini-cli-core/dist/src/core/contentGenerator.js";
|
||||||
|
/**
|
||||||
|
* Create a Gemini CLI Core content generator for a model.
|
||||||
|
*
|
||||||
|
* Purpose: centralize the Code Assist setup and OAuth bootstrap logic in a
|
||||||
|
* reusable package so SF's Gemini provider can stay focused on stream shaping.
|
||||||
|
*
|
||||||
|
* Consumer: the Google Gemini provider in pi-ai.
|
||||||
|
*/
|
||||||
|
export async function createGeminiCliContentGenerator(options) {
|
||||||
|
const cwd = options.cwd ?? process.cwd();
|
||||||
|
const config = makeFakeConfig({
|
||||||
|
model: options.modelId,
|
||||||
|
cwd,
|
||||||
|
targetDir: options.targetDir ?? cwd,
|
||||||
|
});
|
||||||
|
const generatorConfig = await createContentGeneratorConfig(config, AuthType.LOGIN_WITH_GOOGLE);
|
||||||
|
return createContentGenerator(generatorConfig, config);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Discover the active gemini-cli account: tier, project, and every model the
|
||||||
|
* account has access to (with per-model usage fraction and reset time).
|
||||||
|
*
|
||||||
|
* Best-effort: any failure (OAuth expired, no project, network) returns null
|
||||||
|
* silently so callers can downgrade gracefully.
|
||||||
|
*
|
||||||
|
* Consumer: SF-side background catalog cache, usage UI, capacity diagnostics.
|
||||||
|
*/
|
||||||
|
export async function snapshotGeminiCliAccount(cwd) {
|
||||||
|
try {
|
||||||
|
const config = makeFakeConfig({ cwd: cwd ?? process.cwd() });
|
||||||
|
const authClient = await getOauthClient(AuthType.LOGIN_WITH_GOOGLE, config);
|
||||||
|
const userData = await setupUser(authClient, config);
|
||||||
|
const projectId = userData?.projectId;
|
||||||
|
if (!projectId || typeof projectId !== "string")
|
||||||
|
return null;
|
||||||
|
const server = new CodeAssistServer(authClient, projectId, { headers: {} });
|
||||||
|
const data = await server.retrieveUserQuota({ project: projectId });
|
||||||
|
// Dedup buckets per modelId, keeping the WORST quota (lowest
|
||||||
|
// remainingFraction). Code Assist sometimes returns multiple buckets
|
||||||
|
// for the same model when more than one quota window applies; the
|
||||||
|
// pessimistic choice is what every consumer (UI, capacity diagnostics,
|
||||||
|
// model picker) actually wants to surface.
|
||||||
|
const byModel = new Map();
|
||||||
|
for (const b of data?.buckets ?? []) {
|
||||||
|
const modelId = typeof b.modelId === "string" ? b.modelId : "";
|
||||||
|
if (!modelId)
|
||||||
|
continue;
|
||||||
|
const remainingFraction = typeof b.remainingFraction === "number" ? b.remainingFraction : 1;
|
||||||
|
const bucket = {
|
||||||
|
modelId,
|
||||||
|
usedFraction: 1 - remainingFraction,
|
||||||
|
remainingFraction,
|
||||||
|
resetTime: typeof b.resetTime === "string" ? b.resetTime : undefined,
|
||||||
|
};
|
||||||
|
const existing = byModel.get(modelId);
|
||||||
|
if (!existing || bucket.remainingFraction < existing.remainingFraction) {
|
||||||
|
byModel.set(modelId, bucket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const models = Array.from(byModel.values()).sort((a, b) => a.modelId.localeCompare(b.modelId));
|
||||||
|
if (models.length === 0)
|
||||||
|
return null;
|
||||||
|
return {
|
||||||
|
projectId,
|
||||||
|
userTierId: typeof userData?.userTier === "string" ? userData.userTier : undefined,
|
||||||
|
userTierName: userData?.userTierName,
|
||||||
|
paidTier: userData?.paidTier
|
||||||
|
? { id: userData.paidTier.id, name: userData.paidTier.name }
|
||||||
|
: undefined,
|
||||||
|
models,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Convenience wrapper: just the model IDs the active gemini-cli account has
|
||||||
|
* access to. Returns null on failure (same contract as snapshotGeminiCliAccount).
|
||||||
|
*/
|
||||||
|
export async function discoverGeminiCliModels(cwd) {
|
||||||
|
const snap = await snapshotGeminiCliAccount(cwd);
|
||||||
|
if (!snap)
|
||||||
|
return null;
|
||||||
|
return snap.models.map((m) => m.modelId);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=index.js.map
|
||||||
1
packages/google-gemini-cli-provider/src/index.js.map
Normal file
1
packages/google-gemini-cli-provider/src/index.js.map
Normal file
File diff suppressed because one or more lines are too long
2
packages/google-gemini-cli-provider/src/index.test.d.ts
vendored
Normal file
2
packages/google-gemini-cli-provider/src/index.test.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export {};
|
||||||
|
//# sourceMappingURL=index.test.d.ts.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["index.test.ts"],"names":[],"mappings":"","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, test, vi } from \"vitest\";\n\nconst helperState = vi.hoisted(() => ({\n\tauthType: undefined as unknown,\n\tconfigParams: undefined as Record<string, unknown> | undefined,\n}));\n\nvi.mock(\"@google/gemini-cli-core\", () => ({\n\tAuthType: { LOGIN_WITH_GOOGLE: \"LOGIN_WITH_GOOGLE\" },\n\tmakeFakeConfig: vi.fn((params: Record<string, unknown>) => {\n\t\thelperState.configParams = params;\n\t\treturn { params };\n\t}),\n}));\n\nvi.mock(\"@google/gemini-cli-core/dist/src/core/contentGenerator.js\", () => ({\n\tcreateContentGeneratorConfig: vi.fn(async (_config, authType) => {\n\t\thelperState.authType = authType;\n\t\treturn { authType };\n\t}),\n\tcreateContentGenerator: vi.fn(async () => ({\n\t\tasync generateContentStream(): Promise<AsyncGenerator<unknown>> {\n\t\t\treturn (async function* emptyStream() {})();\n\t\t},\n\t})),\n}));\n\nimport { createGeminiCliContentGenerator } from \"./index.js\";\n\ndescribe(\"google-gemini-cli-provider\", () => {\n\ttest(\"createGeminiCliContentGenerator_uses_google_login_auth\", async () => {\n\t\tawait createGeminiCliContentGenerator({ modelId: \"gemini-3-pro\" });\n\n\t\tassert.equal(helperState.authType, \"LOGIN_WITH_GOOGLE\");\n\t\tassert.equal(helperState.configParams?.model, \"gemini-3-pro\");\n\t\tassert.equal(helperState.configParams?.cwd, process.cwd());\n\t\tassert.equal(helperState.configParams?.targetDir, process.cwd());\n\t});\n});\n"]}
|
||||||
35
packages/google-gemini-cli-provider/src/index.test.js
Normal file
35
packages/google-gemini-cli-provider/src/index.test.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, test, vi } from "vitest";
|
||||||
|
const helperState = vi.hoisted(() => ({
|
||||||
|
authType: undefined,
|
||||||
|
configParams: undefined,
|
||||||
|
}));
|
||||||
|
vi.mock("@google/gemini-cli-core", () => ({
|
||||||
|
AuthType: { LOGIN_WITH_GOOGLE: "LOGIN_WITH_GOOGLE" },
|
||||||
|
makeFakeConfig: vi.fn((params) => {
|
||||||
|
helperState.configParams = params;
|
||||||
|
return { params };
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
vi.mock("@google/gemini-cli-core/dist/src/core/contentGenerator.js", () => ({
|
||||||
|
createContentGeneratorConfig: vi.fn(async (_config, authType) => {
|
||||||
|
helperState.authType = authType;
|
||||||
|
return { authType };
|
||||||
|
}),
|
||||||
|
createContentGenerator: vi.fn(async () => ({
|
||||||
|
async generateContentStream() {
|
||||||
|
return (async function* emptyStream() { })();
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
import { createGeminiCliContentGenerator } from "./index.js";
|
||||||
|
describe("google-gemini-cli-provider", () => {
|
||||||
|
test("createGeminiCliContentGenerator_uses_google_login_auth", async () => {
|
||||||
|
await createGeminiCliContentGenerator({ modelId: "gemini-3-pro" });
|
||||||
|
assert.equal(helperState.authType, "LOGIN_WITH_GOOGLE");
|
||||||
|
assert.equal(helperState.configParams?.model, "gemini-3-pro");
|
||||||
|
assert.equal(helperState.configParams?.cwd, process.cwd());
|
||||||
|
assert.equal(helperState.configParams?.targetDir, process.cwd());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//# sourceMappingURL=index.test.js.map
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"file":"index.test.js","sourceRoot":"","sources":["index.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE5C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACrC,QAAQ,EAAE,SAAoB;IAC9B,YAAY,EAAE,SAAgD;CAC9D,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,yBAAyB,EAAE,GAAG,EAAE,CAAC,CAAC;IACzC,QAAQ,EAAE,EAAE,iBAAiB,EAAE,mBAAmB,EAAE;IACpD,cAAc,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAA+B,EAAE,EAAE;QACzD,WAAW,CAAC,YAAY,GAAG,MAAM,CAAC;QAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IACnB,CAAC,CAAC;CACF,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3E,4BAA4B,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QAC/D,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAChC,OAAO,EAAE,QAAQ,EAAE,CAAC;IACrB,CAAC,CAAC;IACF,sBAAsB,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC1C,KAAK,CAAC,qBAAqB;YAC1B,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,WAAW,KAAI,CAAC,CAAC,EAAE,CAAC;QAC7C,CAAC;KACD,CAAC,CAAC;CACH,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,+BAA+B,EAAE,MAAM,YAAY,CAAC;AAE7D,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC3C,IAAI,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,+BAA+B,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;QAEnE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QACxD,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;QAC9D,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, test, vi } from \"vitest\";\n\nconst helperState = vi.hoisted(() => ({\n\tauthType: undefined as unknown,\n\tconfigParams: undefined as Record<string, unknown> | undefined,\n}));\n\nvi.mock(\"@google/gemini-cli-core\", () => ({\n\tAuthType: { LOGIN_WITH_GOOGLE: \"LOGIN_WITH_GOOGLE\" },\n\tmakeFakeConfig: vi.fn((params: Record<string, unknown>) => {\n\t\thelperState.configParams = params;\n\t\treturn { params };\n\t}),\n}));\n\nvi.mock(\"@google/gemini-cli-core/dist/src/core/contentGenerator.js\", () => ({\n\tcreateContentGeneratorConfig: vi.fn(async (_config, authType) => {\n\t\thelperState.authType = authType;\n\t\treturn { authType };\n\t}),\n\tcreateContentGenerator: vi.fn(async () => ({\n\t\tasync generateContentStream(): Promise<AsyncGenerator<unknown>> {\n\t\t\treturn (async function* emptyStream() {})();\n\t\t},\n\t})),\n}));\n\nimport { createGeminiCliContentGenerator } from \"./index.js\";\n\ndescribe(\"google-gemini-cli-provider\", () => {\n\ttest(\"createGeminiCliContentGenerator_uses_google_login_auth\", async () => {\n\t\tawait createGeminiCliContentGenerator({ modelId: \"gemini-3-pro\" });\n\n\t\tassert.equal(helperState.authType, \"LOGIN_WITH_GOOGLE\");\n\t\tassert.equal(helperState.configParams?.model, \"gemini-3-pro\");\n\t\tassert.equal(helperState.configParams?.cwd, process.cwd());\n\t\tassert.equal(helperState.configParams?.targetDir, process.cwd());\n\t});\n});\n"]}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext"}}]}<()>;等待下一步操作。<|assistant to=multi_tool_use.parallel __(/*!json*/)## Step: Rebuild and rerun autonomous SF after tsconfig updates. Also, verify build and run success before marking complete. {
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"lib": ["ES2024"],
|
"lib": ["ES2024"],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"inlineSourceMap": false,
|
"inlineSourceMap": false,
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "Node16",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "Node16",
|
"moduleResolution": "NodeNext",
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
||||||
import { homedir, platform } from "node:os";
|
import { homedir, platform } from "node:os";
|
||||||
import { dirname, join, resolve } from "node:path";
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
|
||||||
77
web/middleware.ts
Normal file
77
web/middleware.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Next.js middleware — validates bearer token and origin on all API routes.
|
||||||
|
*
|
||||||
|
* The SF_WEB_AUTH_TOKEN env var is set at server launch. Every /api/* request
|
||||||
|
* must carry a matching `Authorization: Bearer <token>` header. EventSource
|
||||||
|
* (SSE) connections may use the `_token` query parameter instead since the
|
||||||
|
* EventSource API cannot set custom headers.
|
||||||
|
*
|
||||||
|
* Additionally, if an `Origin` header is present, it must match the expected
|
||||||
|
* localhost origin to prevent cross-site request forgery.
|
||||||
|
*/
|
||||||
|
export function middleware(request: NextRequest): NextResponse {
|
||||||
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
|
// Only gate API routes
|
||||||
|
if (!pathname.startsWith("/api/")) return NextResponse.next();
|
||||||
|
|
||||||
|
const expectedToken = process.env.SF_WEB_AUTH_TOKEN;
|
||||||
|
if (!expectedToken) {
|
||||||
|
// If no token was configured (e.g. dev mode without launch harness),
|
||||||
|
// allow everything — the server didn't opt into auth.
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Origin / CORS check ────────────────────────────────────────────
|
||||||
|
const origin = request.headers.get("origin");
|
||||||
|
if (origin) {
|
||||||
|
const host = process.env.SF_WEB_HOST || "127.0.0.1";
|
||||||
|
const port = process.env.SF_WEB_PORT || "3000";
|
||||||
|
|
||||||
|
// Default: localhost origin for the launched host:port
|
||||||
|
const allowed = new Set([`http://${host}:${port}`]);
|
||||||
|
|
||||||
|
// SF_WEB_ALLOWED_ORIGINS lets users whitelist additional origins for
|
||||||
|
// secure tunnel setups (Tailscale Serve, Cloudflare Tunnel, ngrok, etc.)
|
||||||
|
const extra = process.env.SF_WEB_ALLOWED_ORIGINS;
|
||||||
|
if (extra) {
|
||||||
|
for (const entry of extra.split(",")) {
|
||||||
|
const trimmed = entry.trim();
|
||||||
|
if (trimmed) allowed.add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!allowed.has(origin)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Forbidden: origin mismatch" },
|
||||||
|
{ status: 403 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bearer token check ─────────────────────────────────────────────
|
||||||
|
let token: string | null = null;
|
||||||
|
|
||||||
|
// 1. Authorization header (preferred)
|
||||||
|
const authHeader = request.headers.get("authorization");
|
||||||
|
if (authHeader?.startsWith("Bearer ")) {
|
||||||
|
token = authHeader.slice(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Query parameter fallback for EventSource / SSE
|
||||||
|
if (!token) {
|
||||||
|
token = request.nextUrl.searchParams.get("_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!token || token !== expectedToken) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: "/api/:path*",
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue