Implement GetDel method for space management in JS API
- Added a new GetDel method to the space object, allowing retrieval and immediate deletion of a key's value, enhancing one-time use data handling. - Updated the JS API to include tests for GetDel functionality, ensuring correct behavior for existing and non-existent keys, as well as complex data structures. - Removed debug print statements from the Assistant's initializeCapabilities method for cleaner code.
This commit is contained in:
parent
666c1e8d74
commit
a683452d22
3 changed files with 279 additions and 5 deletions
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/yaoapp/gou/connector"
|
"github.com/yaoapp/gou/connector"
|
||||||
"github.com/yaoapp/gou/connector/openai"
|
"github.com/yaoapp/gou/connector/openai"
|
||||||
"github.com/yaoapp/kun/log"
|
"github.com/yaoapp/kun/log"
|
||||||
"github.com/yaoapp/kun/utils"
|
|
||||||
"github.com/yaoapp/yao/agent/assistant/handlers"
|
"github.com/yaoapp/yao/agent/assistant/handlers"
|
||||||
"github.com/yaoapp/yao/agent/context"
|
"github.com/yaoapp/yao/agent/context"
|
||||||
"github.com/yaoapp/yao/agent/i18n"
|
"github.com/yaoapp/yao/agent/i18n"
|
||||||
|
|
@ -472,10 +471,6 @@ func (ast *Assistant) initializeCapabilities(ctx *context.Context, opts *context
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("--- initializeCapabilities debug ---")
|
|
||||||
utils.Dump(capabilities)
|
|
||||||
fmt.Println("--- end initializeCapabilities debug ---")
|
|
||||||
|
|
||||||
// Set capabilities in context for output adapters to use
|
// Set capabilities in context for output adapters to use
|
||||||
if capabilities != nil {
|
if capabilities != nil {
|
||||||
ctx.Capabilities = capabilities
|
ctx.Capabilities = capabilities
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,40 @@ func (ctx *Context) createSpaceObject(v8ctx *v8go.Context) *v8go.Value {
|
||||||
delFuncVal := delFunc.GetFunction(v8ctx)
|
delFuncVal := delFunc.GetFunction(v8ctx)
|
||||||
obj.Set("Delete", delFuncVal.Value)
|
obj.Set("Delete", delFuncVal.Value)
|
||||||
|
|
||||||
|
// GetDel method: space.GetDel(key) - Get value and delete immediately
|
||||||
|
// Convenient for one-time use data (e.g., file metadata passed between agents)
|
||||||
|
getDelFunc := v8go.NewFunctionTemplate(iso, func(info *v8go.FunctionCallbackInfo) *v8go.Value {
|
||||||
|
if ctx.Space == nil {
|
||||||
|
return v8go.Null(iso)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(info.Args()) < 1 {
|
||||||
|
return bridge.JsException(info.Context(), "GetDel requires a key argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := info.Args()[0].String()
|
||||||
|
|
||||||
|
// Get value first
|
||||||
|
value, err := ctx.Space.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return v8go.Null(iso)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete immediately after getting
|
||||||
|
// Ignore delete errors (key might not exist)
|
||||||
|
ctx.Space.Delete(key)
|
||||||
|
|
||||||
|
// Convert to JavaScript value
|
||||||
|
jsValue, err := bridge.JsValue(info.Context(), value)
|
||||||
|
if err != nil {
|
||||||
|
return v8go.Null(iso)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsValue
|
||||||
|
})
|
||||||
|
getDelFuncVal := getDelFunc.GetFunction(v8ctx)
|
||||||
|
obj.Set("GetDel", getDelFuncVal.Value)
|
||||||
|
|
||||||
return spaceObj
|
return spaceObj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -570,3 +570,248 @@ func TestSpaceErrorHandling(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, true, result["success"], "Should catch Delete error")
|
assert.Equal(t, true, result["success"], "Should catch Delete error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSpaceGetDel tests ctx.space.GetDel method
|
||||||
|
func TestSpaceGetDel(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
ctx := &context.Context{
|
||||||
|
ChatID: "test-chat-id",
|
||||||
|
AssistantID: "test-assistant-id",
|
||||||
|
Locale: "en",
|
||||||
|
Context: stdContext.Background(),
|
||||||
|
Space: plan.NewMemorySharedSpace(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
function test(ctx) {
|
||||||
|
try {
|
||||||
|
// Set a one-time use value
|
||||||
|
ctx.space.Set("one_time_token", "secret_token_12345");
|
||||||
|
|
||||||
|
// Verify it exists before GetDel
|
||||||
|
const before = ctx.space.Get("one_time_token");
|
||||||
|
if (before !== "secret_token_12345") throw new Error("Value not set");
|
||||||
|
|
||||||
|
// Use GetDel - should get value and delete automatically
|
||||||
|
const value = ctx.space.GetDel("one_time_token");
|
||||||
|
|
||||||
|
// Verify value was retrieved
|
||||||
|
if (value !== "secret_token_12345") throw new Error("GetDel returned wrong value");
|
||||||
|
|
||||||
|
// Verify key was deleted
|
||||||
|
const after = ctx.space.Get("one_time_token");
|
||||||
|
if (after !== null && after !== undefined) throw new Error("Key should be deleted after GetDel");
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: value,
|
||||||
|
is_deleted: after === null || after === undefined
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}`, ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Call failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := res.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result["success"].(bool) {
|
||||||
|
t.Fatalf("Test failed: %v", result["error"])
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, true, result["success"], "GetDel should succeed")
|
||||||
|
assert.Equal(t, "secret_token_12345", result["value"], "GetDel should return correct value")
|
||||||
|
assert.Equal(t, true, result["is_deleted"], "Key should be deleted after GetDel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpaceGetDelNonExistentKey tests GetDel on non-existent key
|
||||||
|
func TestSpaceGetDelNonExistentKey(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
ctx := &context.Context{
|
||||||
|
ChatID: "test-chat-id",
|
||||||
|
AssistantID: "test-assistant-id",
|
||||||
|
Locale: "en",
|
||||||
|
Context: stdContext.Background(),
|
||||||
|
Space: plan.NewMemorySharedSpace(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
function test(ctx) {
|
||||||
|
try {
|
||||||
|
// GetDel on non-existent key should return null/undefined
|
||||||
|
const value = ctx.space.GetDel("non_existent_key");
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: value,
|
||||||
|
is_null: value === null || value === undefined
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}`, ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Call failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := res.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, true, result["success"], "GetDel on non-existent key should not throw")
|
||||||
|
assert.Equal(t, true, result["is_null"], "GetDel on non-existent key should return null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpaceGetDelComplexData tests GetDel with complex data structures
|
||||||
|
func TestSpaceGetDelComplexData(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
ctx := &context.Context{
|
||||||
|
ChatID: "test-chat-id",
|
||||||
|
AssistantID: "test-assistant-id",
|
||||||
|
Locale: "en",
|
||||||
|
Context: stdContext.Background(),
|
||||||
|
Space: plan.NewMemorySharedSpace(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
function test(ctx) {
|
||||||
|
try {
|
||||||
|
// Set complex file info data (like voucher assistant use case)
|
||||||
|
const filesInfo = [
|
||||||
|
{
|
||||||
|
file_id: "file123",
|
||||||
|
filename: "invoice.pdf",
|
||||||
|
content_type: "application/pdf",
|
||||||
|
file_type: "pdf",
|
||||||
|
source: "uploader",
|
||||||
|
uploader_name: "__yao.attachment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file_id: "file456",
|
||||||
|
filename: "receipt.png",
|
||||||
|
content_type: "image/png",
|
||||||
|
file_type: "image",
|
||||||
|
source: "uploader",
|
||||||
|
uploader_name: "__yao.attachment"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
ctx.space.Set("workers.voucher:files_info", filesInfo);
|
||||||
|
|
||||||
|
// Use GetDel to retrieve and clean up
|
||||||
|
const retrieved = ctx.space.GetDel("workers.voucher:files_info");
|
||||||
|
|
||||||
|
// Verify data integrity
|
||||||
|
if (!Array.isArray(retrieved)) throw new Error("Should be array");
|
||||||
|
if (retrieved.length !== 2) throw new Error("Length mismatch");
|
||||||
|
if (retrieved[0].file_id !== "file123") throw new Error("First file_id mismatch");
|
||||||
|
if (retrieved[1].filename !== "receipt.png") throw new Error("Second filename mismatch");
|
||||||
|
|
||||||
|
// Verify it's deleted
|
||||||
|
const after = ctx.space.Get("workers.voucher:files_info");
|
||||||
|
if (after !== null && after !== undefined) throw new Error("Should be deleted");
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
files_count: retrieved.length,
|
||||||
|
first_file_id: retrieved[0].file_id,
|
||||||
|
is_deleted: after === null || after === undefined
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}`, ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Call failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := res.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result["success"].(bool) {
|
||||||
|
t.Fatalf("Test failed: %v", result["error"])
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, true, result["success"], "GetDel with complex data should succeed")
|
||||||
|
assert.Equal(t, float64(2), result["files_count"], "Should have 2 files")
|
||||||
|
assert.Equal(t, "file123", result["first_file_id"], "File ID should match")
|
||||||
|
assert.Equal(t, true, result["is_deleted"], "Should be deleted after GetDel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpaceGetDelMultipleCalls tests that GetDel only works once
|
||||||
|
func TestSpaceGetDelMultipleCalls(t *testing.T) {
|
||||||
|
test.Prepare(t, config.Conf)
|
||||||
|
defer test.Clean()
|
||||||
|
|
||||||
|
ctx := &context.Context{
|
||||||
|
ChatID: "test-chat-id",
|
||||||
|
AssistantID: "test-assistant-id",
|
||||||
|
Locale: "en",
|
||||||
|
Context: stdContext.Background(),
|
||||||
|
Space: plan.NewMemorySharedSpace(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := v8.Call(v8.CallOptions{}, `
|
||||||
|
function test(ctx) {
|
||||||
|
try {
|
||||||
|
// Set a value
|
||||||
|
ctx.space.Set("single_use", "use_me_once");
|
||||||
|
|
||||||
|
// First GetDel should work
|
||||||
|
const first = ctx.space.GetDel("single_use");
|
||||||
|
if (first !== "use_me_once") throw new Error("First GetDel failed");
|
||||||
|
|
||||||
|
// Second GetDel should return null (already deleted)
|
||||||
|
const second = ctx.space.GetDel("single_use");
|
||||||
|
if (second !== null && second !== undefined) throw new Error("Second GetDel should return null");
|
||||||
|
|
||||||
|
// Third GetDel should also return null
|
||||||
|
const third = ctx.space.GetDel("single_use");
|
||||||
|
if (third !== null && third !== undefined) throw new Error("Third GetDel should return null");
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
first: first,
|
||||||
|
second_is_null: second === null || second === undefined,
|
||||||
|
third_is_null: third === null || third === undefined
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}`, ctx)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Call failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := res.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected map result, got %T", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !result["success"].(bool) {
|
||||||
|
t.Fatalf("Test failed: %v", result["error"])
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, true, result["success"], "Multiple GetDel calls should work correctly")
|
||||||
|
assert.Equal(t, "use_me_once", result["first"], "First GetDel should return value")
|
||||||
|
assert.Equal(t, true, result["second_is_null"], "Second GetDel should return null")
|
||||||
|
assert.Equal(t, true, result["third_is_null"], "Third GetDel should return null")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue